
/*
  此项目模仿自稚晖君暂未开源的 MonoUI，用于实现类似 UltraLink 的丝滑界面

  有四个版本：

    * 分辨率 128 * 128 ：主菜单，列表和关于本机页基本还原了 UltraLink 的界面和动态效果，电压测量页为原创设计
    * 分辨率 128 * 64 ：主菜单模仿 UltraLink 重新设计，去掉了小标题，电压测量页重新设计，关于本机页面改为列表，列表适配了该分辨率
    * 分辨率 128 * 32 ：在 128 * 64 分辨率的基础上，主界面只保留图标，电压测量页重新设计
    * 通用版本：仅保留列表，主菜单也改为列表，删除电压测量页和与列表无关的动画（保留弹窗效果），经过简单修改可以适配任何分辨率，任何行高度的情况

  WouoUI v2 功能：

    * 全部使用非线性的平滑缓动动画，包括列表，弹窗，甚至进度条
    * 优化平滑动画算法到只有两行，分类别定义平滑权重，并且每个权重值都分别可调
    * 可以打断的非线性动画，当前动画未结束但下一次动画已经被触发时，动画可以自然过渡
    * 非通用版本分别适配了类似 UltraLink 主菜单的磁贴界面（因为让我想起 WP7 的 Metron 风格，所以称之为磁贴）
    * 通用版本仅保留列表类界面，经过简单修改可以适配所有分辨率的屏幕，包括屏幕内行数不是整数的情况
    * 列表菜单，列表可以无限延长
    * 列表文字选择框，选择框可根据选择的字符串长度自动伸缩，进入菜单时从列表开头从长度 0 展开，转到上一级列表时，长度和纵坐标平滑移动到上一级选择的位置
    * 列表单选框，储存数据时也储存该值在列表中所在的位置，展开列表时根据每行开头的字符判断是否绘制外框，再根据位置数据判断是否绘制里面的点
    * 列表多选框，储存数据的数组跟多选框列表的行数对应，不要求连续排列，展开列表时根据每行开头的字符判断是否绘制外框，再根据行数对应的储存数据位置的数值是否为1判断是否绘制里面的点
    * 列表显示数值，与多选框原理相似，但不涉及修改操作
    * 列表展开动画，初始化列表时，可以选择列表从头开始展开，或者从上次选中的位置展开
    * 图标展开动画，初始化磁贴类界面时，可以选择图标从头开始展开，或者从上次选中的位置展开
    * 弹出窗口，实现了窗口弹出的动画效果，可以自定义最大值，最小值，步进值，需要修改的参数等，窗口独立运行，调用非常简单
    * 弹出窗口背景虚化可选项，背景虚化会产生卡顿感，但删掉代码有些可惜，因此做成可选项，默认关闭
    * 亮度调节，在弹出窗口中调节亮度值可以实时改变当前亮度值
    * 旋钮功能，使用EC11旋钮控制，旋钮方向可以软件调整，内置一个USB控制电脑的示例，在睡眠模式下旋转调整音量或者亮度，短按输入一个键值，长按进入主菜单，旋钮消抖时长等参数可以在弹出窗口中调整
    * 循环模式，选择项超过头尾时，选择项跳转到另一侧继续，列表和磁贴类可以分别选择
    * 黑暗模式，其实本来就是黑暗模式，是增加了白天模式，默认开启黑暗模式
    * 消失动画适配两种模式，一种是渐变成全黑，另一种渐变成全白
    * 断电存储，用简单直观的方式将每种功能参数写入EEPROM，只在修改过参数，进入睡眠模式时写入，避免重复擦写，初始化时检查11个标志位，允许一位误码

  项目参考：

    * B站：路徍要什么自行车：在线仿真：https://wokwi.com/projects/350306511434547796，https://www.bilibili.com/video/BV1HA411S7pv/
    * Github：createskyblue：OpenT12：https://github.com/createskyblue/OpenT12
    * Github：peng-zhihui：OpenHeat：https://github.com/peng-zhihui/OpenHeat

  注意事项：

    * 为防止使用者在上传程序后无法直接使用，认为是代码有问题，HID功能默认禁用，如需使用旋钮音量控制和点按输入功能，请在初始化函数中启用相关功能，上传成功后设置两个跳线帽都为0，断电再插上 USB 线

  本项目不设置开源协议，如需商用或借鉴，在醒目处标注本项目开源地址即可
  欢迎关注我的B站账号，用户名：音游玩的人，B站主页：https://space.bilibili.com/9182439?spm_id_from=..0.0
*/

/************************************* 屏幕驱动 *************************************/

//分辨率128*64，可以使用硬件IIC接口
#include <ArduinoJson.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "mqtt.h"

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
#define LED_PIN    6
#define LED_COUNT  70

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

#define   SCL   13
#define   SDA   12
#define   RST   U8X8_PIN_NONE

//风扇引脚
#define FAN1_SPEED_SET_PIN 10
#define FAN2_SPEED_SET_PIN 9
#define FAN3_SPEED_SET_PIN 8
#define FAN4_SPEED_SET_PIN 7

//OLED面板上按钮
#define CONFIRM_BTN_PIN 1
#define BACK_BTN_PIN 2

//WiFi连接超时时间，单位秒
int connectTimeOut_s = 15;                
int is_connect_wifi = -1;
//int is_transfer_mqtt = -1;

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/SCL, /* data=*/SDA);            // OLED SH1106硬件驱动，1.3寸
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);                                         // OLED SSD硬件驱动, 0.96寸

// SHT30 IIC 7位地址默认为0x44
#define Addr_SHT30 0x44
float cTemp = -1;
float fTemp = -1;
float humidity = -1;

int last_sleep_status = -1; // -1:非休眠状态， 1:休眠状态
int sleep_index = 0;

//风扇设置索引
int INDEX_PAGE_FAN_SETTING = 10;
//系统设置索引
int INDEX_PAGE_SYS_SETTING = 12;

//监控面板，风扇滚动文字坐标
int monitor_fan_x = 0;

void init_logo();
void blinkGreenLED();
void ui_param_init();
void updateTemp();
void sleep_proc();

/************************************* 定义页面 *************************************/

//总目录，缩进表示页面层级
enum 
{
  M_WINDOW,
  M_SLEEP,
    M_MAIN, 
      M_EDITOR,
        M_KNOB,
          M_KRF,
          M_KPF,
      M_VOLT,
      M_SETTING,
        M_ABOUT,
        M_FAN_SETTING,
        M_MONITOR,
        M_SYS_SETTING,
};

//状态，初始化标签
enum
{
  S_FADE,       //转场动画
  S_WINDOW,     //弹窗初始化
  S_LAYER_IN,   //层级初始化
  S_LAYER_OUT,  //层级初始化
  S_NONE,       //直接选择页面
};

//菜单结构体
typedef struct MENU
{
  char *m_select;
} M_SELECT;

/************************************* 定义内容 *************************************/

/************************************* 文字内容 *************************************/

M_SELECT main_menu[]
{
  {"监控"},
  {"风扇"},
  {"设置"},
  {"休眠"},
};

M_SELECT editor_menu[]
{
  {"[ 风扇设置 ]"},
  {"- 风扇1"},
  {"- 风扇2"},
  {"- 风扇3"},
  {"- 风扇4"},
};

M_SELECT sys_setting_menu[]
{
  {"[ 系统设置 ]"},  
  {"+ 在线升级"},
  {"+ 远程控制"},
  {"~ 灯光管理"},
  {"~ 休眠时间"},
  {"+ 自动温控"},
  {"~ 触发温度"},
  {"~ 设定风扇转速"},
  {"- [ 关于 ]"},
};

M_SELECT knob_menu[]
{
  {"[ Knob ]"},
  {"# Rotate Func"},
  {"$ Press Func"},
};

M_SELECT krf_menu[]
{
  {"[ Rotate Function ]"},
  {"--------------------------"},
  {"= Disable"},
  {"--------------------------"},
  {"= Volume"},
  {"= Brightness"},
  {"--------------------------"},
};

M_SELECT kpf_menu[]
{
  {"[ Press Function ]"},
  {"--------------------------"},
  {"= Disable"},
  {"--------------------------"},
  {"= A"},
  {"= B"},
  {"= C"},
  {"= D"},
  {"= E"},
  {"= F"},
  {"= G"},
  {"= H"},
  {"= I"},
  {"= J"},
  {"= K"},
  {"= L"},
  {"= M"},
  {"= N"},
  {"= O"},
  {"= P"},
  {"= Q"},
  {"= R"},
  {"= S"},
  {"= T"},
  {"= U"},
  {"= V"},
  {"= W"},
  {"= X"},
  {"= Y"},
  {"= Z"},
  {"--------------------------"},
  {"= 0"},
  {"= 1"},
  {"= 2"},
  {"= 3"},
  {"= 4"},
  {"= 5"},
  {"= 6"},
  {"= 7"},
  {"= 8"},
  {"= 9"},
  {"--------------------------"},
  {"= Esc"},
  {"= F1"},
  {"= F2"},
  {"= F3"},
  {"= F4"},
  {"= F5"},
  {"= F6"},
  {"= F7"},
  {"= F8"},
  {"= F9"},
  {"= F10"},
  {"= F11"},
  {"= F12"},
  {"--------------------------"},
  {"= Left Ctrl"},
  {"= Left Shift"},
  {"= Left Alt"},
  {"= Left Win"},
  {"= Right Ctrl"},
  {"= Right Shift"},
  {"= Right Alt"},
  {"= Right Win"},
  {"--------------------------"},
  {"= Caps Lock"},
  {"= Backspace"},
  {"= Return"},
  {"= Insert"},
  {"= Delete"},
  {"= Tab"},
  {"--------------------------"},
  {"= Home"},
  {"= End"},
  {"= Page Up"},
  {"= Page Down"},
  {"--------------------------"},
  {"= Up Arrow"},
  {"= Down Arrow"},
  {"= Left Arrow"},
  {"= Right Arrow"},
  {"--------------------------"},
};

M_SELECT volt_menu[]
{
  {"A0"},
  {"A1"},
  {"A2"},
  {"A3"},
  {"A4"},
  {"A5"},
  {"A6"},
  {"A7"},
  {"B0"},
  {"B1"},
};

M_SELECT setting_menu[]
{
  {"[ Setting ]"},
  {"~ Disp Bri"},
  {"~ Tile Ani"},
  {"~ List Ani"},
  {"~ Win Ani"},
  {"~ Spot Ani"},
  {"~ Tag Ani"},
  {"~ Fade Ani"},
  {"~ Btn SPT"},
  {"~ Btn LPT"},
  {"+ T Ufd Fm Scr"},
  {"+ L Ufd Fm Scr"},
  {"+ T Loop Mode"},
  {"+ L Loop Mode"},
  {"+ Win Bokeh Bg"},
  {"+ Knob Rot Dir"},
  {"+ Dark Mode"},
  {"- [ About ]"},
};

M_SELECT about_menu[]
{
  {"[ WouoUI ]"},
  {"- Version: v2.3"},
  {"- Board: STM32F103"},
  {"- Ram: 20k"},
  {"- Flash: 64k"},
  {"- Freq: 72Mhz"},
  {"- Creator: RQNG"},
  {"- Bili UID: 9182439"},  
};

M_SELECT fan_setting_menu[]
{
  {"[ 设置 ]"},  
  {"~ 风扇1"},
  {"~ 风扇2"},
  {"~ 风扇3"},
  {"~ 风扇4"},
};

M_SELECT monitor_menu[]
{
  {"[ WouoUI ]"},  
  {"~ MONITOR"},  
};


/************************************* 图片内容 *************************************/

#define logo_width 128
#define logo_height 64
static const unsigned char logo_bits[] U8X8_PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0xC0, 0xE1, 0xFF, 0xE0, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xF9, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, 
  0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xED, 0xFF, 
  0xE0, 0xE0, 0xE1, 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0xD8, 0xED, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0x7F, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xED, 0xE0, 0xE0, 0xE0, 0xE1, 0x00, 
  0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xE7, 0xE0, 
  0xE0, 0xFF, 0xFF, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0xC0, 0xE1, 0xFF, 0xE0, 0xFF, 0xFF, 0x00, 0xF8, 0xFF, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xEF, 0xFF, 0xE0, 0x00, 0x00, 0xF0, 
  0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xEF, 0xFF, 
  0xE0, 0xFE, 0xFF, 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0xF8, 0xEF, 0xE0, 0xE0, 0xFE, 0xFF, 0xF0, 0x7F, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE1, 0xE0, 0xE0, 0x0E, 0xF0, 0x00, 
  0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE7, 0xE0, 
  0xE0, 0x0E, 0xE0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0xF0, 0xE7, 0xFF, 0xF0, 0xFE, 0xFF, 0x00, 0xF8, 0xFF, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xEF, 0xFF, 0x70, 0xFE, 0xFF, 0xF8, 
  0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xED, 0xFF, 
  0x70, 0x0E, 0xE0, 0xF8, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0xFC, 0xE5, 0xE0, 0x70, 0xFE, 0xFF, 0xF8, 0x7B, 0xC0, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xE1, 0xE0, 0x70, 0xFE, 0xFF, 0x00, 
  0x70, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xE1, 0xE0, 
  0x78, 0x0E, 0xE0, 0x00, 0x70, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0xC8, 0xF9, 0xFF, 0x7B, 0xFE, 0xFF, 0x00, 0xF0, 0xF0, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF9, 0xFF, 0x3B, 0xFE, 0xFF, 0x00, 
  0xF0, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF9, 0xFF, 
  0x3B, 0xFE, 0xFF, 0x00, 0xF0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0xC0, 0x01, 0x00, 0x10, 0x0E, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF0, 0xFF, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xFF, 0xFB, 0xFF, 
  0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 
  0x00, 0xFF, 0x1B, 0xC3, 0x30, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x0E, 0x00, 0x30, 0x18, 0xC3, 0xF0, 0xFF, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x1F, 0x30, 0x18, 0xC3, 
  0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 
  0x3F, 0x30, 0xF8, 0xFF, 0xF0, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0xCE, 0x71, 0x30, 0x18, 0xC3, 0x30, 0x86, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0x61, 0x30, 0x18, 0xC3, 
  0x50, 0x96, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 
  0x61, 0x30, 0x18, 0xC3, 0xF8, 0xBE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0xCE, 0x61, 0x30, 0xF8, 0xFF, 0x18, 0xB6, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0x71, 0x30, 0x18, 0x83, 
  0x99, 0xE7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 
  0x3F, 0x30, 0x00, 0x83, 0x79, 0x9E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x0E, 0x1F, 0x30, 0x00, 0x83, 0x19, 0x86, 0x01, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 
  0x0C, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x8C, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 
};


PROGMEM const uint8_t main_icon_pic[][120]
{
  {
    0xF0, 0xFF, 0xFF, 0x03, 0xFC, 0xFF, 0xFF, 0x1F, 0x02, 0x00, 0x00, 0x10, 
    0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x3E, 0x03, 0x00, 0x00, 0x3F, 
    0x03, 0x00, 0x80, 0x3F, 0x03, 0x00, 0x80, 0x3F, 0x03, 0x00, 0xC0, 0x3F, 
    0x03, 0x00, 0xE0, 0x37, 0x03, 0x00, 0xE0, 0x37, 0x03, 0x00, 0xF0, 0x33, 
    0x03, 0x0E, 0xF8, 0x31, 0x83, 0x1F, 0xFC, 0x31, 0xC3, 0x3F, 0xFC, 0x30, 
    0xE3, 0xFF, 0x7E, 0x30, 0xFB, 0xFF, 0x3F, 0x30, 0xFF, 0xFF, 0x3F, 0x30, 
    0xFF, 0xF9, 0x1F, 0x30, 0xFF, 0xE0, 0x0F, 0x30, 0x7F, 0xC0, 0x0F, 0x30, 
    0x3F, 0xC0, 0x07, 0x30, 0x1F, 0x80, 0x03, 0x30, 0x0F, 0x00, 0x00, 0x30, 
    0x03, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0x30, 
    0x02, 0x00, 0x00, 0x10, 0xFE, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x07, 
  },
  {
    0xF0, 0xFF, 0xFF, 0x03, 0xFC, 0xFF, 0xFF, 0x1F, 0x02, 0x00, 0x02, 0x10, 
    0x02, 0xC0, 0x07, 0x30, 0x03, 0xF0, 0x0F, 0x30, 0x03, 0xF8, 0x07, 0x30, 
    0x03, 0xF8, 0x07, 0x30, 0x03, 0xF8, 0x03, 0x30, 0x03, 0xF8, 0x03, 0x30, 
    0x03, 0xF0, 0x03, 0x30, 0x03, 0xF0, 0x03, 0x30, 0x7B, 0xE0, 0xE1, 0x31, 
    0xFB, 0xEF, 0xFB, 0x33, 0xFB, 0xFF, 0xFF, 0x37, 0xFB, 0x7F, 0xFE, 0x37, 
    0xFB, 0x3F, 0xFC, 0x37, 0xF3, 0x7F, 0xFE, 0x3F, 0xF3, 0xE7, 0xF7, 0x3F, 
    0xE3, 0xE3, 0xA1, 0x37, 0xC3, 0xF0, 0x03, 0x32, 0x03, 0xF0, 0x07, 0x30, 
    0x03, 0xF0, 0x07, 0x30, 0x03, 0xF0, 0x0F, 0x30, 0x03, 0xF0, 0x0F, 0x30, 
    0x03, 0xF8, 0x07, 0x30, 0x03, 0xF8, 0x03, 0x30, 0x02, 0xF8, 0x01, 0x30, 
    0x02, 0x30, 0x00, 0x10, 0xFE, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x07,  
  },
  {
    0xF0, 0xFF, 0xFF, 0x03, 0xFC, 0xFF, 0xFF, 0x1F, 0x02, 0x00, 0x00, 0x10, 
    0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x30, 
    0x03, 0xE0, 0x01, 0x30, 0x03, 0xF0, 0x01, 0x30, 0x03, 0xF1, 0x23, 0x30, 
    0x83, 0xFF, 0x7F, 0x30, 0xC3, 0xFF, 0xFF, 0x30, 0xC3, 0xFF, 0x7F, 0x30, 
    0x83, 0x0F, 0x7C, 0x30, 0x83, 0x07, 0x7C, 0x30, 0x83, 0x07, 0x78, 0x30, 
    0xE3, 0x07, 0xF8, 0x30, 0xE3, 0x07, 0xF8, 0x31, 0xE3, 0x07, 0xF8, 0x31, 
    0xE3, 0x0F, 0xFC, 0x31, 0xC3, 0x3F, 0xFF, 0x30, 0x03, 0xFE, 0x1F, 0x30, 
    0x03, 0xFE, 0x1F, 0x30, 0x03, 0xFE, 0x1F, 0x30, 0x03, 0x3E, 0x1F, 0x30, 
    0x03, 0x18, 0x06, 0x30, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0x30, 
    0x02, 0x00, 0x00, 0x10, 0xFE, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x07, 
  },
  {
    0xF0, 0xFF, 0xFF, 0x03, 0xFC, 0xFF, 0xFF, 0x1F, 0x06, 0x00, 0x00, 0x18, 
    0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x30, 
    0x03, 0x00, 0x00, 0x30, 0x03, 0xC0, 0x00, 0x30, 0x03, 0xC8, 0x04, 0x30, 
    0x03, 0xDC, 0x0C, 0x30, 0x03, 0xC6, 0x18, 0x30, 0x03, 0xC3, 0x30, 0x30, 
    0x03, 0xC3, 0x60, 0x30, 0x83, 0x01, 0x60, 0x30, 0x83, 0x01, 0x60, 0x30, 
    0x83, 0x01, 0x60, 0x30, 0x83, 0x01, 0x60, 0x30, 0x83, 0x01, 0x60, 0x30, 
    0x83, 0x03, 0x60, 0x30, 0x03, 0x03, 0x30, 0x30, 0x03, 0x06, 0x38, 0x30, 
    0x03, 0x1E, 0x1E, 0x30, 0x03, 0xF8, 0x07, 0x30, 0x03, 0xE0, 0x01, 0x30, 
    0x03, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0x30, 
    0x06, 0x00, 0x00, 0x18, 0xFE, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x07, 
  },
};

/************************************* 页面变量 *************************************/

//OLED变量
#define   DISP_W              128   //屏幕宽度
#define   DISP_H              64    //屏幕高度
uint8_t   *buf_ptr;                 //指向屏幕缓冲的指针
uint16_t  buf_len;                  //缓冲长度

//UI变量
#define   UI_DEPTH            20    //最深层级数
#define   UI_MNUMB            100   //菜单数量
#define   UI_PARAM            27    //参数数量

//0~15为系统保留字段
//16~19为第二个页面的存储参数，分别为风扇1~4转速
//20~24为第三个页面的存储参数
enum 
{
  DISP_BRI,           //屏幕亮度
  TILE_ANI,           //磁贴动画速度
  LIST_ANI,           //列表动画速度
  WIN_ANI,            //弹窗动画速度
  SPOT_ANI,           //聚光动画速度
  TAG_ANI,            //标签动画速度
  FADE_ANI,           //消失动画速度
  BTN_SPT,            //按键短按时长
  BTN_LPT,            //按键长按时长
  TILE_UFD,           //磁贴图标从头展开开关
  LIST_UFD,           //菜单列表从头展开开关
  TILE_LOOP,          //磁贴图标循环模式开关
  LIST_LOOP,          //菜单列表循环模式开关
  WIN_BOK,            //弹窗背景虚化开关
  KNOB_DIR,           //旋钮方向切换开关
  DARK_MODE,          //黑暗模式开关
  FAN1_SPEED,         //风扇1设置速度
  FAN2_SPEED,         //风扇2设置速度
  FAN3_SPEED,         //风扇3设置速度
  FAN4_SPEED,         //风扇4设置速度
  IS_UPDATE,            //开启在线升级
  IS_REMOTE_CONTROL,    //开启远程控制
  LED_TYPE,             //LED灯光类型
  SLEEP_TIME,           //默认休眠时间
  AUTO_TEMP_CONTROL,    //自动温度控制
  TRIGGER_TEMPERATURE,  //触发温度
  TRIGGER_FAN_RPM,      //设定转速
};

struct 
{
  bool      init;
  uint8_t   num[UI_MNUMB];
  uint8_t   select[UI_DEPTH];
  uint8_t   layer;
  uint8_t   index = M_SLEEP;
  uint8_t   state = S_NONE;
  bool      sleep = true;
  uint8_t   fade = 1;
  uint8_t   param[UI_PARAM];
} ui;

//磁贴变量
//所有磁贴页面都使用同一套参数
#define   TILE_B_FONT         u8g2_font_helvB18_tr        //磁贴大标题字体
#define   TILE_B_CHINESE_FONT u8g2_font_wqy16_t_gb2312b
#define   TILE_B_TITLE_H      18                          //磁贴大标题字体高度
#define   TILE_ICON_H         30                          //磁贴图标高度
#define   TILE_ICON_W         30                          //磁贴图标宽度
#define   TILE_ICON_S         36                          //磁贴图标间距
#define   TILE_INDI_H         27                          //磁贴大标题指示器高度
#define   TILE_INDI_W         7                           //磁贴大标题指示器宽度
#define   TILE_INDI_S         36                          //磁贴大标题指示器上边距
struct 
{
  float   title_y_calc = TILE_INDI_S + (TILE_INDI_H - TILE_B_TITLE_H) / 2 + TILE_B_TITLE_H * 2;
  float   title_y_trg_calc = TILE_INDI_S + (TILE_INDI_H - TILE_B_TITLE_H) / 2 + TILE_B_TITLE_H;
  int16_t temp;
  bool    select_flag;
  float   icon_x;
  float   icon_x_trg;
  float   icon_y;
  float   icon_y_trg;
  float   indi_x; 
  float   indi_x_trg;
  float   title_y;
  float   title_y_trg;
} tile;

//列表变量
//默认参数

// #define   LIST_FONT           u8g2_font_HelvetiPixel_tr   //列表字体
#define   LIST_FONT           u8g2_font_wqy12_t_gb2312b   //列表字体
#define   LIST_TEXT_H         8                           //列表每行文字字体的高度
#define   LIST_LINE_H         16                          //列表单行高度
#define   LIST_TEXT_S         4                           //列表每行文字的上边距，左边距和右边距，下边距由它和字体高度和行高度决定
#define   LIST_BAR_W          5                           //列表进度条宽度，需要是奇数，因为正中间有1像素宽度的线
#define   LIST_BOX_R          0.5f                        //列表选择框圆角

/*
//超窄行高度测试
#define   LIST_FONT           u8g2_font_4x6_tr            //列表字体
#define   LIST_TEXT_H         5                           //列表每行文字字体的高度
#define   LIST_LINE_H         7                           //列表单行高度
#define   LIST_TEXT_S         1                           //列表每行文字的上边距，左边距和右边距，下边距由它和字体高度和行高度决定
#define   LIST_BAR_W          7                           //列表进度条宽度，需要是奇数，因为正中间有1像素宽度的线
#define   LIST_BOX_R          0.5f                        //列表选择框圆角
*/
struct
{
  uint8_t line_n = DISP_H / LIST_LINE_H;
  int16_t temp;
  bool    loop;
  float   y;
  float   y_trg;
  float   box_x;
  float   box_x_trg;
  float   box_y;
  float   box_y_trg[UI_DEPTH];
  float   bar_y;
  float   bar_y_trg;
} list;

//电压测量页面变量
//开发板模拟引脚
uint8_t   analog_pin[1] = { 12};
//曲线相关
#define   WAVE_SAMPLE         20                          //采集倍数
#define   WAVE_W              94                          //波形宽度
#define   WAVE_L              24                          //波形左边距
#define   WAVE_U              0                           //波形上边距
#define   WAVE_MAX            27                          //最大值
#define   WAVE_MIN            4                           //最小值
#define   WAVE_BOX_H          32                          //波形边框高度
#define   WAVE_BOX_W          94                          //波形边框宽度
#define   WAVE_BOX_L_S        24                          //波形边框左边距
//列表和文字背景框相关
#define   VOLT_FONT           u8g2_font_helvB18_tr        //电压数字字体
#define   VOLT_TEXT_BG_L_S    24                          //文字背景框左边距
#define   VOLT_TEXT_BG_W      94                          //文字背景框宽度
#define   VOLT_TEXT_BG_H      29                          //文字背景框高度
struct
{
  int     ch0_adc[WAVE_SAMPLE * WAVE_W];
  int     ch0_wave[WAVE_W];
  int     val;
  float   text_bg_r; 
  float   text_bg_r_trg; 
} volt;


//选择框变量

//默认参数
#define   CHECK_BOX_L_S       95                          //选择框在每行的左边距
#define   CHECK_BOX_U_S       2                           //选择框在每行的上边距
#define   CHECK_BOX_F_W       12                          //选择框外框宽度
#define   CHECK_BOX_F_H       12                          //选择框外框高度
#define   CHECK_BOX_D_S       2                           //选择框里面的点距离外框的边距

/*
//超窄行高度测试
#define   CHECK_BOX_L_S       99                          //选择框在每行的左边距
#define   CHECK_BOX_U_S       0                           //选择框在每行的上边距
#define   CHECK_BOX_F_W       5                           //选择框外框宽度
#define   CHECK_BOX_F_H       5                           //选择框外框高度
#define   CHECK_BOX_D_S       1                           //选择框里面的点距离外框的边距
*/
struct
{
  uint8_t *v;
  uint8_t *m;
  uint8_t *s;
  uint8_t *s_p;
} check_box;

//弹窗变量
// #define   WIN_FONT            u8g2_font_HelvetiPixel_tr   //弹窗字体
#define   WIN_FONT            u8g2_font_wqy12_t_gb2312   //弹窗字体
#define   WIN_H               32                          //弹窗高度
#define   WIN_W               102                         //弹窗宽度
#define   WIN_BAR_W           92                          //弹窗进度条宽度
#define   WIN_BAR_H           7                           //弹窗进度条高度
#define   WIN_Y               - WIN_H - 2                 //弹窗竖直方向出场起始位置
#define   WIN_Y_TRG           - WIN_H - 2                 //弹窗竖直方向退场终止位置
struct
{
  //uint8_t
  uint8_t   *value;
  uint8_t   max;
  uint8_t   min;
  uint8_t   step;

  MENU      *bg;
  uint8_t   index;
  char      title[20];
  uint8_t   select;
  uint8_t   l = (DISP_W - WIN_W) / 2;
  uint8_t   u = (DISP_H - WIN_H) / 2;
  float     bar;
  float     bar_trg;
  float     y;
  float     y_trg;
} win;

//聚光灯变量
struct
{
  float   l; 
  float   l_trg; 
  float   r; 
  float   r_trg; 
  float   u; 
  float   u_trg; 
  float   d; 
  float   d_trg; 
} spot;

/************************************* USB 相关 *************************************/

// #include <USBComposite.h>

// USBHID HID;

// const uint8_t reportDescription[] = 
// {
//    HID_CONSUMER_REPORT_DESCRIPTOR(),
//    HID_KEYBOARD_REPORT_DESCRIPTOR()
// };

// HIDConsumer Consumer(HID);
// HIDKeyboard Keyboard(HID);

// void hid_init()
// {
//   HID.begin(reportDescription, sizeof(reportDescription));
//   while (!USBComposite);
// }

/********************************** 自定义功能变量 **********************************/

//旋钮功能变量
#define   KNOB_PARAM          4
#define   KNOB_DISABLE        0
#define   KNOB_ROT_VOL        1
#define   KNOB_ROT_BRI        2
enum 
{
  KNOB_ROT,       //睡眠下旋转旋钮的功能，0 禁用，1 音量，2 亮度
  KNOB_COD,       //睡眠下短按旋钮输入的字符码，0 禁用
  KNOB_ROT_P,     //旋转旋钮功能在单选框中选择的位置
  KNOB_COD_P,     //字符码在单选框中选择的位置
};
struct 
{
  uint8_t param[KNOB_PARAM] = { KNOB_DISABLE, KNOB_DISABLE, 2, 2 }; //禁用在列表的第2个选项，第0个是标题，第1个是分界线
} knob;

/************************************* 断电保存 *************************************/

#include <EEPROM.h>

//EEPROM变量
#define   EEPROM_CHECK        30
struct
{
  bool    init;
  bool    change;
  int     address;
  uint8_t check;
  uint8_t check_param[EEPROM_CHECK] = { 'a', 'b', 'c', 'd', 'e', 'f','g', 'h', 'i', 'j', 'k' }; 
} eeprom;

//EEPROM写数据，回到睡眠时执行一遍
// void eeprom_write_all_data()
// {
//   eeprom.address = 0;
//   for (uint8_t i = 0; i < EEPROM_CHECK; ++i)    EEPROM.write(eeprom.address + i, eeprom.check_param[i]);  eeprom.address += EEPROM_CHECK;
//   for (uint8_t i = 0; i < UI_PARAM; ++i)        EEPROM.write(eeprom.address + i, ui.param[i]);            eeprom.address += UI_PARAM;
//   for (uint8_t i = 0; i < KNOB_PARAM; ++i)      EEPROM.write(eeprom.address + i, knob.param[i]);          eeprom.address += KNOB_PARAM;
// }

//EEPROM读数据，开机初始化时执行一遍
// void eeprom_read_all_data()
// {
//   eeprom.address = EEPROM_CHECK;   
//   for (uint8_t i = 0; i < UI_PARAM; ++i)        ui.param[i]   = EEPROM.read(eeprom.address + i);          eeprom.address += UI_PARAM;
//   for (uint8_t i = 0; i < KNOB_PARAM; ++i)      knob.param[i] = EEPROM.read(eeprom.address + i);          eeprom.address += KNOB_PARAM;
// }


//存储UI-Param
void eeprom_write_all_data()
{
  for (uint8_t i = 0; i < UI_PARAM; ++i) {
    EEPROM.write(i, ui.param[i]);
  }
  EEPROM.commit();
}


void eeprom_read_all_data()
{
  for (uint8_t i = 0; i < UI_PARAM; ++i) {
    ui.param[i] = EEPROM.read(i);
    Serial.print(i);    
    Serial.print("=");
    Serial.println(EEPROM.read(i));
  }
}

//写入rom
void eeprom_save(int key, int value) {

  eeprom_read_all_data();

  Serial.print(key);
  Serial.print(":");
  Serial.println(value);
  EEPROM.write(key, value);
  EEPROM.commit();
}


void eeprom_init()
{
  EEPROM.begin(4096);
  Serial.print("初始化值:");
  Serial.println(EEPROM.read(16));
  //检查电扇位是否默认值
  if(EEPROM.read(16) == 0) {
    ui_param_init();
  } else {
    eeprom_read_all_data();  
  }  
}

//开机检查是否已经修改过，没修改过则跳过读配置步骤，用默认设置
// void eeprom_init()
// {
//   eeprom.check = 0;
//   eeprom.address = 0; for (uint8_t i = 0; i < EEPROM_CHECK; ++i)  if (EEPROM.read(eeprom.address + i) != eeprom.check_param[i])  eeprom.check ++;
//   if (eeprom.check <= 1) eeprom_read_all_data();  //允许一位误码
//   else ui_param_init();
// }

/************************************* 旋钮相关 *************************************/

//可按下旋钮引脚
#define   AIO   4
#define   BIO   5
#define   SW    3

//按键ID
#define   BTN_ID_CC           0   //逆时针旋转
#define   BTN_ID_CW           1   //顺时针旋转
#define   BTN_ID_SP           2   //短按
#define   BTN_ID_LP           3   //长按

//按键变量
#define   BTN_PARAM_TIMES     2   //由于uint8_t最大值可能不够，但它存储起来方便，这里放大两倍使用
struct
{
  uint8_t   id;
  bool      flag;
  bool      pressed;
  bool      CW_1;
  bool      CW_2;
  bool      val;
  bool      val_last;  
  bool      alv;  
  bool      blv;
  long      count;
} volatile btn;

void knob_inter() 
{
  btn.alv = digitalRead(AIO);
  btn.blv = digitalRead(BIO);
  if (!btn.flag && btn.alv == LOW) 
  {
    btn.CW_1 = btn.blv;
    btn.flag = true;
  }
  if (btn.flag && btn.alv) 
  {
    btn.CW_2 = !btn.blv;
    if (btn.CW_1 && btn.CW_2)
     {
      btn.id = ui.param[KNOB_DIR];
      btn.pressed = true;
    }
    if (btn.CW_1 == false && btn.CW_2 == false) 
    {
      btn.id = !ui.param[KNOB_DIR];
      btn.pressed = true;
    }
    btn.flag = false;
  }
}

void btn_scan() 
{
  btn.val = digitalRead(SW);
  if (btn.val != btn.val_last)
  {
    btn.val_last = btn.val;
    delay(ui.param[BTN_SPT] * BTN_PARAM_TIMES);
    btn.val = digitalRead(SW);
    if (btn.val == LOW)
    {
      btn.pressed = true;
      btn.count = 0;
      while (!digitalRead(SW))
      {
        btn.count++;
        delay(1);
      }
      if (btn.count < ui.param[BTN_LPT] * BTN_PARAM_TIMES)  btn.id = BTN_ID_SP;
      else  btn.id = BTN_ID_LP;
    }
  }
}

void btn_init() 
{
  pinMode(AIO, INPUT);
  pinMode(BIO, INPUT);
  pinMode(SW, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(AIO), knob_inter, CHANGE);

  pinMode(BACK_BTN_PIN, INPUT_PULLUP);
}

/************************************ 初始化函数 ***********************************/

/********************************* 初始化数据处理函数 *******************************/

//显示数值的初始化
void check_box_v_init(uint8_t *param)
{
  check_box.v = param;
}

//多选框的初始化
void check_box_m_init(uint8_t *param)
{
  check_box.m = param;
}

//单选框时的初始化
void check_box_s_init(uint8_t *param, uint8_t *param_p)
{
  check_box.s = param;
  check_box.s_p = param_p;
}

//多选框处理函数
void check_box_m_select(uint8_t param)
{
  check_box.m[param] = !check_box.m[param];
  eeprom.change = true;
}

//单选框处理函数
void check_box_s_select(uint8_t val, uint8_t pos)
{
  *check_box.s = val;
  *check_box.s_p = pos;
  eeprom.change = true;
}

//弹窗数值初始化
void window_value_init(char title[], uint8_t select, uint8_t *value, uint8_t max, uint8_t min, uint8_t step, MENU *bg, uint8_t index)
{
  strcpy(win.title, title);
  win.select = select;
  win.value = value;
  win.max = max;
  win.min = min;
  win.step = step;
  win.bg = bg;
  win.index = index;  
  ui.index = M_WINDOW;
  ui.state = S_WINDOW;
}

/*********************************** UI 初始化函数 *********************************/

//在初始化EEPROM时，选择性初始化的默认设置
//一共30个参数
void ui_param_init()
{
  ui.param[DISP_BRI]  = 255;      //屏幕亮度
  ui.param[TILE_ANI]  = 30;       //磁贴动画速度
  ui.param[LIST_ANI]  = 60;       //列表动画速度
  ui.param[WIN_ANI]   = 25;       //弹窗动画速度
  ui.param[SPOT_ANI]  = 50;       //聚光动画速度
  ui.param[TAG_ANI]   = 60;       //标签动画速度
  ui.param[FADE_ANI]  = 30;       //消失动画速度
  ui.param[BTN_SPT]   = 25;       //按键短按时长
  ui.param[BTN_LPT]   = 150;      //按键长按时长
  ui.param[TILE_UFD]  = 1;        //磁贴图标从头展开开关
  ui.param[LIST_UFD]  = 1;        //菜单列表从头展开开关
  ui.param[TILE_LOOP] = 0;        //磁贴图标循环模式开关
  ui.param[LIST_LOOP] = 0;        //菜单列表循环模式开关
  ui.param[WIN_BOK]   = 0;        //弹窗背景虚化开关
  ui.param[KNOB_DIR]  = 0;        //旋钮方向切换开关   
  ui.param[DARK_MODE] = 1;        //黑暗模式开关
  ui.param[FAN1_SPEED] = 0;        
  ui.param[FAN2_SPEED] = 0; 
  ui.param[FAN3_SPEED] = 0;        
  ui.param[FAN4_SPEED] = 0;
  ui.param[IS_UPDATE] = 0;        
  ui.param[IS_REMOTE_CONTROL] = 1; 
  ui.param[LED_TYPE] = 0;             // 0:跑马灯 1:流水灯 2:彩虹灯        
  ui.param[SLEEP_TIME] = 30;           //息屏时间 10|20|30
  ui.param[AUTO_TEMP_CONTROL] = 0;           //自动温控 0:关闭 | 1:开启
  ui.param[TRIGGER_TEMPERATURE] = 30;  //风扇触发温度,当温度高于x℃的时候，启动电扇，当温度低于x摄氏度，则关闭风扇
  ui.param[TRIGGER_FAN_RPM] = 90;     //温控被触发后，所有风扇设定转速比 
}

//列表类页面列表行数初始化，必须初始化的参数
void ui_init()
{
  ui.num[M_MAIN]      = sizeof( main_menu     )   / sizeof(M_SELECT);
  ui.num[M_EDITOR]    = sizeof( editor_menu   )   / sizeof(M_SELECT);
  ui.num[M_KNOB]      = sizeof( knob_menu     )   / sizeof(M_SELECT);
  ui.num[M_KRF]       = sizeof( krf_menu      )   / sizeof(M_SELECT);
  ui.num[M_KPF]       = sizeof( kpf_menu      )   / sizeof(M_SELECT);
  ui.num[M_VOLT]      = sizeof( volt_menu     )   / sizeof(M_SELECT);
  ui.num[M_SETTING]   = sizeof( setting_menu  )   / sizeof(M_SELECT);
  ui.num[M_ABOUT]     = sizeof( about_menu    )   / sizeof(M_SELECT);   
  ui.num[M_FAN_SETTING]     = sizeof( fan_setting_menu    )   / sizeof(M_SELECT);   
  ui.num[M_MONITOR]     = sizeof( monitor_menu    )   / sizeof(M_SELECT);
  ui.num[M_SYS_SETTING]     = sizeof( sys_setting_menu    )   / sizeof(M_SELECT);
  
}

/********************************* 分页面初始化函数 ********************************/

//进入磁贴类时的初始化
void tile_param_init()
{
  ui.init = false;
  tile.icon_x = 0;
  tile.icon_x_trg = TILE_ICON_S;
  tile.icon_y = -TILE_ICON_H;
  tile.icon_y_trg = 0;
  tile.indi_x = 0;
  tile.indi_x_trg = TILE_INDI_W;
  tile.title_y = tile.title_y_calc;
  tile.title_y_trg = tile.title_y_trg_calc;
}

//进入睡眠时的初始化
void sleep_param_init()
{
  u8g2.setDrawColor(0);
  u8g2.drawBox(0, 0, DISP_W, DISP_H);
  u8g2.setPowerSave(1);
  ui.state = S_NONE;  
  ui.sleep = false;
  if (eeprom.change)
  {
    eeprom_write_all_data();
    eeprom.change = false;
  }  
}

//旋钮设置页初始化
void knob_param_init() { check_box_v_init(knob.param); }

//旋钮旋转页初始化
void krf_param_init() { check_box_s_init(&knob.param[KNOB_ROT], &knob.param[KNOB_ROT_P]); }

//旋钮点按页初始化
void kpf_param_init() { check_box_s_init(&knob.param[KNOB_COD], &knob.param[KNOB_COD_P]); }

//电压测量页初始化
void volt_param_init()
{
  volt.text_bg_r = 0;
  volt.text_bg_r_trg = VOLT_TEXT_BG_W; 
}

//设置页初始化
void setting_param_init()
{
  check_box_v_init(ui.param);
  check_box_m_init(ui.param);
}

/********************************** 通用初始化函数 *********************************/

/*
  页面层级管理逻辑是，把所有页面都先当作列表类初始化，不是列表类按需求再初始化对应函数
  这样做会浪费一些资源，但跳转页面时只需要考虑页面层级，逻辑上更清晰，减少出错
*/

//弹窗动画初始化
void window_param_init()
{
  win.bar = 0;
  win.y = WIN_Y;
  win.y_trg = win.u;
  ui.state = S_NONE;
}

//进入更深层级时的初始化
void layer_init_in()
{
  ui.layer ++;
  ui.init = 0;
  list.y = 0;
  list.y_trg = LIST_LINE_H;
  list.box_x = 0;
  list.box_y = 0;
  list.bar_y = 0;
  ui.state = S_FADE;
  switch (ui.index)
  {
    case M_MAIN:    tile_param_init();    break;  //睡眠进入主菜单，动画初始化   
    case M_KNOB:    knob_param_init();    break;  //旋钮设置页，行末尾文字初始化
    case M_KRF:     krf_param_init();     break;  //旋钮旋转页，单选框初始化  
    case M_KPF:     kpf_param_init();     break;  //旋钮点按页，单选框初始化  
    case M_VOLT:    volt_param_init();    break;  //主菜单进入电压测量页，动画初始化
    case M_SETTING: setting_param_init(); break;  //主菜单进入设置页，单选框初始化
    case M_MONITOR: setting_param_init(); break;  //主菜单进入设置页，单选框初始化
    case M_FAN_SETTING: setting_param_init(); break;  //主菜单进入设置页，单选框初始化
    case M_SYS_SETTING: setting_param_init(); break;  //主菜单进入设置页，单选框初始化
  }
}

//进入更浅层级时的初始化
void layer_init_out()
{
  ui.select[ui.layer] = 0;
  list.box_y_trg[ui.layer] = 0;
  ui.layer --;
  ui.init = 0;
  list.y = 0;
  list.y_trg = LIST_LINE_H;
  list.bar_y = 0;
  ui.state = S_FADE;
  switch (ui.index)
  {
    case M_SLEEP: sleep_param_init(); break;    //主菜单进入睡眠页，检查是否需要写EEPROM
    case M_MAIN:  tile_param_init();  break;    //不管什么页面进入主菜单时，动画初始化
  }
}

/************************************* 动画函数 *************************************/

//动画函数
void animation(float *a, float *a_trg, uint8_t n)
{
  if (*a != *a_trg)
  {
    if (fabs(*a - *a_trg) < 0.15f) *a = *a_trg;
    else *a += (*a_trg - *a) / (ui.param[n] / 10.0f);
  }
}

//消失函数
void fade()
{
  delay(ui.param[FADE_ANI]);
  if (ui.param[DARK_MODE])
  {
    switch (ui.fade)
    {
      case 1: for (uint16_t i = 0; i < buf_len; ++i)  if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] & 0xAA; break;
      case 2: for (uint16_t i = 0; i < buf_len; ++i)  if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] & 0x00; break;
      case 3: for (uint16_t i = 0; i < buf_len; ++i)  if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] & 0x55; break;
      case 4: for (uint16_t i = 0; i < buf_len; ++i)  if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] & 0x00; break;
      default: ui.state = S_NONE; ui.fade = 0; break;
    }
  }
  else
  {
    switch (ui.fade)
    {
      case 1: for (uint16_t i = 0; i < buf_len; ++i)  if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] | 0xAA; break;
      case 2: for (uint16_t i = 0; i < buf_len; ++i)  if (i % 2 != 0) buf_ptr[i] = buf_ptr[i] | 0x00; break;
      case 3: for (uint16_t i = 0; i < buf_len; ++i)  if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] | 0x55; break;
      case 4: for (uint16_t i = 0; i < buf_len; ++i)  if (i % 2 == 0) buf_ptr[i] = buf_ptr[i] | 0x00; break;
      default: ui.state = S_NONE; ui.fade = 0; break;
    }    
  }
  ui.fade++;
}

/************************************* 显示函数 *************************************/

//磁贴类页面通用显示函数
void tile_show(struct MENU arr_1[], const uint8_t icon_pic[][120])
{
  //计算动画过渡值
  animation(&tile.icon_x, &tile.icon_x_trg, TILE_ANI);
  animation(&tile.icon_y, &tile.icon_y_trg, TILE_ANI);
  animation(&tile.indi_x, &tile.indi_x_trg, TILE_ANI);
  animation(&tile.title_y, &tile.title_y_trg, TILE_ANI);

  //设置大标题的颜色，0透显，1实显，2反色，这里用实显
  u8g2.setDrawColor(1);

  //绘制大标题
  // u8g2.setFont(TILE_B_FONT); 
  u8g2.setFont(TILE_B_CHINESE_FONT);
  // u8g2.drawStr(((DISP_W - TILE_INDI_W) - u8g2.getStrWidth(arr_1[ui.select[ui.layer]].m_select)) / 2 + TILE_INDI_W, tile.title_y, arr_1[ui.select[ui.layer]].m_select);
  u8g2.setCursor(((DISP_W - TILE_INDI_W) - u8g2.getStrWidth(arr_1[ui.select[ui.layer]].m_select)) / 2 + TILE_INDI_W, tile.title_y);
  u8g2.print(arr_1[ui.select[ui.layer]].m_select);
  //绘制大标题指示器
  u8g2.drawBox(0, TILE_ICON_S, tile.indi_x, TILE_INDI_H);

  //绘制图标
  if (!ui.init)
  {
    for (uint8_t i = 0; i < ui.num[ui.index]; ++i)  
    {
      if (ui.param[TILE_UFD]) tile.temp = (DISP_W - TILE_ICON_W) / 2 + i * tile.icon_x - TILE_ICON_S * ui.select[ui.layer];
      else tile.temp = (DISP_W - TILE_ICON_W) / 2 + (i - ui.select[ui.layer]) * tile.icon_x;
      u8g2.drawXBMP(tile.temp, (int16_t)tile.icon_y, TILE_ICON_W, TILE_ICON_H, icon_pic[i]); 
    }
    if (tile.icon_x == tile.icon_x_trg) 
    {
      ui.init = true;
      tile.icon_x = tile.icon_x_trg = - ui.select[ui.layer] * TILE_ICON_S;
    }
  }
  else for (uint8_t i = 0; i < ui.num[ui.index]; ++i) u8g2.drawXBMP((DISP_W - TILE_ICON_W) / 2 + (int16_t)tile.icon_x + i * TILE_ICON_S, 0, TILE_ICON_W, TILE_ICON_H, icon_pic[i]);

  //反转屏幕内元素颜色，白天模式遮罩
  u8g2.setDrawColor(2);
  if (!ui.param[DARK_MODE]) u8g2.drawBox(0, 0, DISP_W, DISP_H);
}

/*************** 根据列表每行开头符号，判断每行尾部是否绘制以及绘制什么内容 *************/

//列表显示数值
void list_draw_value(int n) { 
  //判断是哪个页面
  //u8g2.print(check_box.v[n - 1]); 
  //如果是风扇设置页面
  if (ui.index == INDEX_PAGE_FAN_SETTING) {
    u8g2.print(check_box.v[n + 15]);
  } 
  //如果是系统设置页面
  else if(ui.index == INDEX_PAGE_SYS_SETTING)
  {
    u8g2.print(check_box.v[n + 19]);
  }
}

//绘制外框
void list_draw_check_box_frame() { u8g2.drawRFrame(CHECK_BOX_L_S, list.temp + CHECK_BOX_U_S, CHECK_BOX_F_W, CHECK_BOX_F_H, 1); }

//绘制框里面的点
void list_draw_check_box_dot() { u8g2.drawBox(CHECK_BOX_L_S + CHECK_BOX_D_S + 1, list.temp + CHECK_BOX_U_S + CHECK_BOX_D_S + 1, CHECK_BOX_F_W - (CHECK_BOX_D_S + 1) * 2, CHECK_BOX_F_H - (CHECK_BOX_D_S + 1) * 2); }

//列表显示旋钮功能
void list_draw_krf(int n) 
{ 
  switch (check_box.v[n - 1])
  {
    case 0: u8g2.print("OFF"); break;
    case 1: u8g2.print("VOL"); break;
    case 2: u8g2.print("BRI"); break;
  }
}

//列表显示按键键值
void list_draw_kpf(int n) 
{ 
  if (check_box.v[n - 1] == 0) u8g2.print("OFF");
  else if (check_box.v[n - 1] <= 90) u8g2.print((char)check_box.v[n - 1]);
  else u8g2.print("?");
}

//判断列表尾部内容
void list_draw_text_and_check_box(struct MENU arr[], int i)
{
  //u8g2.drawStr(LIST_TEXT_S, list.temp + LIST_TEXT_H + LIST_TEXT_S, arr[i].m_select);
  u8g2.setCursor(LIST_TEXT_S, list.temp + LIST_TEXT_H + LIST_TEXT_S);
  u8g2.print(arr[i].m_select);
  
  u8g2.setCursor(CHECK_BOX_L_S, list.temp + LIST_TEXT_H + LIST_TEXT_S);


  switch (arr[i].m_select[0])
  {
    case '~': list_draw_value(i); break;
    case '+': 
    {
      // list_draw_check_box_frame(); if (check_box.m[i - 1] == 1)  list_draw_check_box_dot(); 
      list_draw_check_box_frame(); 
      //如果是FAN1
      int t = 0;
      //如果是选择主菜单中的FAN1
      if(ui.index == INDEX_PAGE_SYS_SETTING) {
        t = i + 19;
      }     
      if (check_box.m[t] == 1) {
        list_draw_check_box_dot();
      }
    }
    break;
    case '=': list_draw_check_box_frame(); if (*check_box.s_p == i)      list_draw_check_box_dot(); break;
    case '#': list_draw_krf(i);   break;
    case '$': list_draw_kpf(i);   break;
  }
}

/******************************** 列表显示函数 **************************************/

//列表类页面通用显示函数
void list_show(struct MENU arr[], uint8_t ui_index)
{
  //更新动画目标值
  u8g2.setFont(LIST_FONT);
  //指示栏设置最大
  list.box_x_trg = DISP_W - 10;
  // list.box_x_trg = u8g2.getStrWidth(arr[ui.select[ui.layer]].m_select) + LIST_TEXT_S * 2;
  list.bar_y_trg = ceil((ui.select[ui.layer]) * ((float)DISP_H / (ui.num[ui_index] - 1)));
  
  //计算动画过渡值
  animation(&list.y, &list.y_trg, LIST_ANI);
  animation(&list.box_x, &list.box_x_trg, LIST_ANI);
  animation(&list.box_y, &list.box_y_trg[ui.layer], LIST_ANI);
  animation(&list.bar_y, &list.bar_y_trg, LIST_ANI);

  //检查循环动画是否结束
  if (list.loop && list.box_y == list.box_y_trg[ui.layer]) list.loop = false;

  //设置文字和进度条颜色，0透显，1实显，2反色，这里都用实显
  u8g2.setDrawColor(1);
  
  //绘制进度条
  u8g2.drawHLine(DISP_W - LIST_BAR_W, 0, LIST_BAR_W);
  u8g2.drawHLine(DISP_W - LIST_BAR_W, DISP_H - 1, LIST_BAR_W);
  u8g2.drawVLine(DISP_W - ceil((float)LIST_BAR_W / 2), 0, DISP_H);
  u8g2.drawBox(DISP_W - LIST_BAR_W, 0, LIST_BAR_W, list.bar_y);

  //绘制列表文字  
  if (!ui.init)
  {
    for (int i = 0; i < ui.num[ui_index]; ++ i)
    {
      if (ui.param[LIST_UFD]) list.temp = i * list.y - LIST_LINE_H * ui.select[ui.layer] + list.box_y_trg[ui.layer];
      else list.temp = (i - ui.select[ui.layer]) * list.y + list.box_y_trg[ui.layer];
      list_draw_text_and_check_box(arr, i);
    }
    if (list.y == list.y_trg) 
    {
      ui.init = true;
      list.y = list.y_trg = - LIST_LINE_H * ui.select[ui.layer] + list.box_y_trg[ui.layer];
    }
  }
  else for (int i = 0; i < ui.num[ui_index]; ++ i)
  {
    list.temp = LIST_LINE_H * i + list.y;
    list_draw_text_and_check_box(arr, i);
  }

  //绘制文字选择框，0透显，1实显，2反色，这里用反色
  u8g2.setDrawColor(2);
  u8g2.drawRBox(0, list.box_y, list.box_x, LIST_LINE_H, LIST_BOX_R);

  //反转屏幕内元素颜色，白天模式遮罩，在这里屏蔽有列表参与的页面，使遮罩作用在那个页面上
  if (!ui.param[DARK_MODE])
  {
    u8g2.drawBox(0, 0, DISP_W, DISP_H);
    switch(ui.index)
    {
      case M_WINDOW: 
      case M_VOLT:
      u8g2.drawBox(0, 0, DISP_W, DISP_H);  
    }
  }
}

//电压页面显示函数
// void volt_show()
// {
//   //使用列表类显示选项
//   list_show(volt_menu, M_VOLT); 

//   //计算动画过渡值  
//   animation(&volt.text_bg_r, &volt.text_bg_r_trg, TAG_ANI);

//   //设置曲线颜色，0透显，1实显，2反色，这里用实显
//   u8g2.setDrawColor(1);  

//   //绘制电压曲线和外框
//   volt.val = 0;
//   u8g2.drawFrame(WAVE_BOX_L_S, 0, WAVE_BOX_W, WAVE_BOX_H);
//   u8g2.drawFrame(WAVE_BOX_L_S + 1, 1, WAVE_BOX_W - 2, WAVE_BOX_H - 2);
//   if (list.box_y == list.box_y_trg[ui.layer] && list.y == list.y_trg)
//   {
//     for (int i = 0; i < WAVE_SAMPLE * WAVE_W; i++) volt.ch0_adc[i] = volt.val = analogRead(analog_pin[ui.select[ui.layer]]);
//     for (int i = 1; i < WAVE_W - 1; i++)
//     { 
//       volt.ch0_wave[i] = map(volt.ch0_adc[int(5 * i)], 0, 4095, WAVE_MAX, WAVE_MIN);   
//       u8g2.drawLine(WAVE_L + i - 1, WAVE_U + volt.ch0_wave[i - 1], WAVE_L + i, WAVE_U + volt.ch0_wave[i]);
//     }
//   }

//   //绘制电压值
//   u8g2.setFontDirection(0);
//   u8g2.setFont(VOLT_FONT); 
//   u8g2.setCursor(39, DISP_H - 6);
//   u8g2.print(volt.val / 4096.0f * 3.3f);
//   u8g2.print("V");

//   //绘制列表选择框和电压文字背景
//   u8g2.setDrawColor(2);
//   u8g2.drawBox(VOLT_TEXT_BG_L_S, DISP_H - VOLT_TEXT_BG_H, volt.text_bg_r, VOLT_TEXT_BG_H);

//   //反转屏幕内元素颜色，白天模式遮罩
//   if (!ui.param[DARK_MODE]) u8g2.drawBox(0, 0, DISP_W, DISP_H);
// }

//弹窗通用显示函数
void window_show()
{
  //绘制背景列表，根据开关判断背景是否要虚化
  list_show(win.bg, win.index);
  if (ui.param[WIN_BOK]) for (uint16_t i = 0; i < buf_len; ++i)  buf_ptr[i] = buf_ptr[i] & (i % 2 == 0 ? 0x55 : 0xAA);

  //更新动画目标值
  u8g2.setFont(WIN_FONT);
  win.bar_trg = (float)(*win.value - win.min) / (float)(win.max - win.min) * (WIN_BAR_W - 4);

  //计算动画过渡值
  animation(&win.bar, &win.bar_trg, WIN_ANI);
  animation(&win.y, &win.y_trg, WIN_ANI);

  //绘制窗口
  u8g2.setDrawColor(0); u8g2.drawRBox(win.l, (int16_t)win.y, WIN_W, WIN_H, 2);    //绘制外框背景
  u8g2.setDrawColor(1); u8g2.drawRFrame(win.l, (int16_t)win.y, WIN_W, WIN_H, 2);  //绘制外框描边
  u8g2.drawRFrame(win.l + 5, (int16_t)win.y + 20, WIN_BAR_W, WIN_BAR_H, 1);       //绘制进度条外框
  u8g2.drawBox(win.l + 7, (int16_t)win.y + 22, win.bar, WIN_BAR_H - 4);           //绘制进度条
  u8g2.setCursor(win.l + 5, (int16_t)win.y + 14); u8g2.print(win.title);          //绘制标题
  u8g2.setCursor(win.l + 78, (int16_t)win.y + 14); u8g2.print(*win.value);        //绘制当前值
  
  //需要在窗口修改参数时立即见效的函数
  if (!strcmp(win.title, "Disp Bri")) u8g2.setContrast(ui.param[DISP_BRI]);

  //反转屏幕内元素颜色，白天模式遮罩
  u8g2.setDrawColor(2);
  if (!ui.param[DARK_MODE]) u8g2.drawBox(0, 0, DISP_W, DISP_H);
}

/************************************* 处理函数 *************************************/

/*********************************** 通用处理函数 ***********************************/

//磁贴类页面旋转时判断通用函数
void tile_rotate_switch()
{
  switch (btn.id)
  { 
    case BTN_ID_CC:
      if (ui.init)
      {
        if (ui.select[ui.layer] > 0)
        {
          ui.select[ui.layer] -= 1;
          tile.icon_x_trg += TILE_ICON_S;
          tile.select_flag = false;
          last_sleep_status = -1;
          Serial.println("11111111111111111");
          last_sleep_status = -1;
        }
        else 
        {
          if (ui.param[TILE_LOOP])
          {
            ui.select[ui.layer] = ui.num[ui.index] - 1;
            tile.icon_x_trg = - TILE_ICON_S * (ui.num[ui.index] - 1);
            Serial.println("222222222222222222");
            break;
          }
          else 
          {
            Serial.println("33333333333333333333333");
            tile.select_flag = true;
            last_sleep_status = -1;
          }
          
        }
      }
      break;

    case BTN_ID_CW:
      if (ui.init)
      {
        if (ui.select[ui.layer] < (ui.num[ui.index] - 1)) 
        {
          ui.select[ui.layer] += 1;
          tile.icon_x_trg -= TILE_ICON_S;
          tile.select_flag = false; 
          Serial.println("444444444444444");
          last_sleep_status = -1;
        }
        else 
        {
          if (ui.param[TILE_LOOP])
          {
            ui.select[ui.layer] = 0;
            tile.icon_x_trg = 0;
            Serial.println("55555555555555");
            break;
          }
          else{
            Serial.println("6666666666666666");
            tile.select_flag = true;
          } 
        }
      }
      break;
  }
}

//列表类页面旋转时判断通用函数
void list_rotate_switch()
{
  if (!list.loop)
  {
    switch (btn.id)
    {
      case BTN_ID_CC:
        Serial.println("BTN_ID_CC");
        last_sleep_status = -1;
        if (ui.select[ui.layer] == 0)
        {
          if (ui.param[LIST_LOOP] && ui.init)
          {
            list.loop = true;
            ui.select[ui.layer] = ui.num[ui.index] - 1;
            if (ui.num[ui.index] > list.line_n) 
            {
              list.box_y_trg[ui.layer] = DISP_H - LIST_LINE_H;
              list.y_trg = DISP_H - ui.num[ui.index] * LIST_LINE_H;
            }
            else list.box_y_trg[ui.layer] = (ui.num[ui.index] - 1) * LIST_LINE_H;
            break;
          }
          else break;
        }
        if (ui.init)
        {
          ui.select[ui.layer] -= 1;
          if (ui.select[ui.layer] < - (list.y_trg / LIST_LINE_H)) 
          {
            if (!(DISP_H % LIST_LINE_H)) list.y_trg += LIST_LINE_H;
            else
            {
              if (list.box_y_trg[ui.layer] == DISP_H - LIST_LINE_H * list.line_n)
              {
                list.y_trg += (list.line_n + 1) * LIST_LINE_H - DISP_H;
                list.box_y_trg[ui.layer] = 0;
              }
              else if (list.box_y_trg[ui.layer] == LIST_LINE_H)
              {
                list.box_y_trg[ui.layer] = 0;
              }
              else list.y_trg += LIST_LINE_H;
            }
          }
          else list.box_y_trg[ui.layer] -= LIST_LINE_H;
          break;
        }

      case BTN_ID_CW:
        Serial.println("BTN_ID_CW");
        last_sleep_status = -1;
        if (ui.select[ui.layer] == (ui.num[ui.index] - 1))
        {
          if (ui.param[LIST_LOOP] && ui.init)
          {
            list.loop = true;
            ui.select[ui.layer] = 0;
            list.y_trg = 0;
            list.box_y_trg[ui.layer] = 0;
            break;
          }
          else break;
        }
        if (ui.init)
        {
          ui.select[ui.layer] += 1;
          if ((ui.select[ui.layer] + 1) > (list.line_n - list.y_trg / LIST_LINE_H))
          {
            if (!(DISP_H % LIST_LINE_H)) list.y_trg -= LIST_LINE_H;
            else
            {
              if (list.box_y_trg[ui.layer] == LIST_LINE_H * (list.line_n - 1))
              {
                list.y_trg -= (list.line_n + 1) * LIST_LINE_H - DISP_H;
                list.box_y_trg[ui.layer] = DISP_H - LIST_LINE_H;
              }
              else if (list.box_y_trg[ui.layer] == DISP_H - LIST_LINE_H * 2)
              {
                list.box_y_trg[ui.layer] = DISP_H - LIST_LINE_H;
              }
              else list.y_trg -= LIST_LINE_H;
            }
          }
          else list.box_y_trg[ui.layer] += LIST_LINE_H;
          break;
        }
        break;
    }
  }
}

//弹窗通用处理函数
void window_proc()
{
  window_show();
  if (win.y == WIN_Y_TRG) ui.index = win.index;
  if (btn.pressed && win.y == win.y_trg && win.y != WIN_Y_TRG)
  {
    btn.pressed = false;
    switch (btn.id)
    {
      case BTN_ID_CW: if (*win.value < win.max)  *win.value += win.step;  eeprom.change = true;  break;
      case BTN_ID_CC: if (*win.value > win.min)  *win.value -= win.step;  eeprom.change = true;  break;  
      case BTN_ID_SP: case BTN_ID_LP: win.y_trg = WIN_Y_TRG; break;
    }
  }
}

void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
  for(int a=0; a<10; a++) {  // Repeat 10 times...
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      strip.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in steps of 3...
      for(int c=b; c<strip.numPixels(); c += 3) {
        strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      strip.show(); // Update strip with new contents
      delay(wait);  // Pause for a moment
    }
  }
}

// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(int wait) {
  // Hue of first pixel runs 5 complete loops through the color wheel.
  // Color wheel has a range of 65536 but it's OK if we roll over, so
  // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
  // means we'll make 5*65536/256 = 1280 passes through this outer loop:
  for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
    for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
      // Offset pixel hue by an amount to make one full revolution of the
      // color wheel (range of 65536) along the length of the strip
      // (strip.numPixels() steps):
      int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
      // strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
      // optionally add saturation and value (brightness) (each 0 to 255).
      // Here we're using just the single-argument hue variant. The result
      // is passed through strip.gamma32() to provide 'truer' colors
      // before assigning to each pixel:
      strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
    }
    strip.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
  }
}

// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void theaterChaseRainbow(int wait) {
  int firstPixelHue = 0;     // First pixel starts at red (hue 0)
  for(int a=0; a<30; a++) {  // Repeat 30 times...
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      strip.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in increments of 3...
      for(int c=b; c<strip.numPixels(); c += 3) {
        // hue of pixel 'c' is offset by an amount to make one full
        // revolution of the color wheel (range 65536) along the length
        // of the strip (strip.numPixels() steps):
        int      hue   = firstPixelHue + c * 65536L / strip.numPixels();
        uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
        strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      strip.show();                // Update strip with new contents
      delay(wait);                 // Pause for a moment
      firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
    }
  }
}

//测试LED闪烁
void blinkGreenLED(){
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
      strip.setPixelColor(i, strip.Color(  0, 255, 0));         //  Set pixel's color (in RAM)
      strip.show();     
      delay(100);             
  }
  delay(800);
  //关灯，造成闪烁效果
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, strip.Color(  0, 0 , 0));         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match                          
  }
  delay(800);
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
      strip.setPixelColor(i, strip.Color(  0, 255, 0));         //  Set pixel's color (in RAM)
      strip.show();     
      //delay(100);             
  }
  delay(800);
  //关灯，造成闪烁效果
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, strip.Color(  0, 0 , 0));         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match                          
  }
  delay(800);
   for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
      strip.setPixelColor(i, strip.Color(  0, 255, 0));         //  Set pixel's color (in RAM)
      strip.show();     
      //delay(100);             
  }
  delay(800);
  //关灯，造成闪烁效果
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, strip.Color(  0, 0 , 0));         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match                          
  }
  delay(800);
}

const char *mqtt_broker = "broker.emqx.io";
const char *topic_publish = "esp32/zengk/topic";
const char *topic_subscribe = "esp32/zengk/subscribe";
const char *mqtt_username = "emqx";
const char *mqtt_password = "public";
const int mqtt_port = 1883;

//初始化WIFI
void init_basic_wifi() 
{
    init_wifi();
    is_connect_wifi = 1;
} 

int is_started_wifi = -1;
//检查wifi
void check_wifi()
{    
   if(ui.param[IS_REMOTE_CONTROL] == 1) {
      if(is_started_wifi == -1) {
        is_started_wifi = 1;              
        //init_basic_wifi();          
        Serial.println("started wifi...");
      }
      if(is_started_wifi == 1) {
        //检查wifi
        // handleMqtt();
        Serial.println("checking wifi...");
      }       
    }else{
      if(is_started_wifi == 1) {
        is_started_wifi = -1;
        //WiFi.disconnect();      
        Serial.println("shutdown wifi...");
      }
    }
}

/********************************** 风扇处理函数 **********************************/
void init_fans(){
  pinMode(FAN1_SPEED_SET_PIN, OUTPUT);
  pinMode(FAN2_SPEED_SET_PIN, OUTPUT);  
}
/********************************** 风扇处理函数END **********************************/

/********************************** FREERTOS函数 **********************************/
TaskHandle_t task1; // 任务1结构对象
TaskHandle_t task2;
TaskHandle_t task3;
TaskHandle_t task_monitor;   //任务监控

static int task1_flag;           // 用于指示任务运行状态的标志变量
static int task2_flag;           
static int task3_flag;     

int last_led_status = -1;
int last_fan1_status = -1;
int last_fan2_status = -1;
int last_fan3_status = -1;
int last_fan4_status = -1;
int last_remote_ctl_status = -1;
String output;
// int is_started_wifi = -1;
/**
 * 任务的运行代码
 * 100ms处理任务
 * @param param 任务初始运行参数
 */
// other message
unsigned long duration = 0;     //  加权时间
const int read_times = 2;       //  测速信号采样周期（多取几次可更精确）
const int fan_min_Hz = 40;      //  测速线每秒最低脉冲次数，不同的风扇不一样，需要根据转速范围计算，或者实测也行
const int fan_speed = 12; 
static void task1_process(void *arg)
{
    static const char *TASK1_TAG = "TASK1";
    while (1) {
        //设置x坐标
        monitor_fan_x ++;        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}


static void task2_process(void *arg)
{
    static const char *TASK2_TAG = "TASK2";
    while (1) {
        if(last_fan1_status !=  ui.param[FAN1_SPEED]){
            last_fan1_status = ui.param[FAN1_SPEED];            
            analogWrite(FAN1_SPEED_SET_PIN, last_fan1_status*0.01*255);
            Serial.print("FAN1 Speed:");
            Serial.println(last_fan1_status*0.01*255);
            eeprom_write_all_data();
        }
        if(last_fan2_status !=  ui.param[FAN2_SPEED]){
            last_fan2_status = ui.param[FAN2_SPEED];            
            analogWrite(FAN2_SPEED_SET_PIN, last_fan2_status*0.01*255);
            Serial.print("FAN2 Speed:");
            Serial.println(last_fan2_status*0.01*255);
            eeprom_write_all_data();
        }
         if(last_fan3_status !=  ui.param[FAN3_SPEED]){
            last_fan3_status = ui.param[FAN3_SPEED];            
            analogWrite(FAN3_SPEED_SET_PIN, last_fan3_status*0.01*255);
            Serial.print("FAN3 Speed:");
            Serial.println(last_fan3_status*0.01*255);
            eeprom_write_all_data();
        }
        if(last_fan4_status !=  ui.param[FAN4_SPEED]){
            last_fan4_status = ui.param[FAN4_SPEED];            
            analogWrite(FAN4_SPEED_SET_PIN, last_fan4_status*0.01*255);
            Serial.print("FAN4 Speed:");
            Serial.println(last_fan4_status*0.01*255);
            eeprom_write_all_data();
        }    

        //处理MQTT协议
        handleMqtt();
       

        //检查休眠状态
        //30s无动作进入休眠模式
        int sleepTime = ui.param[SLEEP_TIME];
        if(sleepTime < 30) {
          sleepTime = 30;
        }
        if(sleep_index >= sleepTime && last_sleep_status == -1) {
          sleep_index = 0;
          last_sleep_status = 1;          
          Serial.println("Turn in sleep status.");
          ui.index = M_SLEEP;
        } 
        sleep_index ++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// MQTT消息回调函数，该函数会在PubSubClient对象的loop方法中被调用
void mqtt_callback(char *topic, byte *payload, unsigned int length)
{
    Serial.printf("Message arrived in topic %s, length %d\n", topic, length);
    Serial.print("Message:");
    for (int i = 0; i < length; i++)
    {
        Serial.print((char)payload[i]);
    }
    is_transfer_mqtt = 1;

    DynamicJsonDocument doc(1024);
    DeserializationError error = deserializeJson(doc, payload); //反序列化JSON数据
    
    Serial.println("\n----------------START----------------");
    if (!error) //检查反序列化是否成功
    {
        JsonObject root = doc.as<JsonObject>();
        //读取json节点
        const char *type = root["type"]; 
        const char *action = root["action"]; 
        Serial.print("type:");
        Serial.println(type);
        Serial.print("action:");
        Serial.println(action); 
        //获取当前系统配置
        DynamicJsonDocument tempJson(350);
        char buffer[350];
        if( strcmp(type, "get") == 0 ){
            Serial.println("Get Action.");
            if( strcmp(action,"getall") == 0 ){
              tempJson["fan1Speed"] = ui.param[FAN1_SPEED];
              tempJson["fan2Speed"] = ui.param[FAN2_SPEED];
              tempJson["fan3Speed"] = ui.param[FAN3_SPEED];
              tempJson["fan4Speed"] = ui.param[FAN4_SPEED];
              tempJson["isUpdate"] = ui.param[IS_UPDATE];
              tempJson["isRemoteControl"] = ui.param[IS_REMOTE_CONTROL];
              tempJson["ledType"] = ui.param[LED_TYPE];
              tempJson["sleepTime"] = ui.param[SLEEP_TIME];        
              tempJson["autTempControl"] = ui.param[AUTO_TEMP_CONTROL];
              tempJson["triggerTemp"] = ui.param[TRIGGER_TEMPERATURE];
              tempJson["triggerFanRPM"] = ui.param[TRIGGER_FAN_RPM];
              tempJson["temperature"] = cTemp;
              tempJson["humidity"] = humidity;
            }else if(strcmp(action,"gettemp") == 0){
              tempJson["temperature"] = cTemp;
              tempJson["humidity"] = humidity;
            }    
            serializeJson(tempJson, buffer);
            sendMessage( buffer );
        } else {          
            Serial.println("Set Action."); 
            if( strcmp(action,"fan") == 0 ){ 
                const char *fan1Speed = root["fan1Speed"]; 
                const char *fan2Speed = root["fan2Speed"];
                const char *fan3Speed = root["fan3Speed"];
                const char *fan4Speed = root["fan4Speed"]; 
                                
                Serial.print("FAN1:");
                Serial.println(atoi(fan1Speed));
                ui.param[FAN1_SPEED] = atoi(fan1Speed);
                ui.param[FAN2_SPEED] = atoi(fan2Speed);
                ui.param[FAN3_SPEED] = atoi(fan3Speed);
                ui.param[FAN4_SPEED] = atoi(fan4Speed);              
            } else if ( strcmp(action,"ledtype") == 0 )
            {
                const char *ledtype = root["ledtype"]; 
                const char *value = root["value"];
                Serial.print("LED TYPE:");
                Serial.println(atoi(value));
                ui.param[LED_TYPE] = atoi(value);
            }
            
            tempJson["type"] = "set";
            tempJson["status"] = "done";     
            serializeJson(tempJson, buffer);
            sendMessage( buffer );
        }       
    } else
    {
        Serial.println("bad json.");  
    }
    Serial.println("\n----------------END----------------");
}

void clearLED()
{
  for(int i=0; i<=strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, strip.Color(  0, 0 , 0));         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match                          
  }
}

int mqtt_status_bar_stepper = 0;
int is_transfer_mqtt = -1;

static void task3_process(void *arg)
{
    static const char *TASK3_TAG = "TASK3";
    while (1) {
        // ESP_LOGI(TASK3_TAG, "task3_flag = %d, arg3 = %s", task3_flag, (char *)arg);        
        if(last_led_status != ui.param[LED_TYPE]) {
          last_led_status = ui.param[LED_TYPE];
          Serial.print("LED_TYPE:");
          Serial.println(last_led_status);
          if(last_led_status == 0) {
            blinkGreenLED(); 
          } else if(last_led_status == 1) {
            // Fill along the length of the strip in various colors...
            colorWipe(strip.Color(255,   0,   0), 50); // Red
            colorWipe(strip.Color(  0, 255,   0), 50); // Green
            colorWipe(strip.Color(  0,   0, 255), 50); // Blue 
          } else if(last_led_status == 2) {
            // Do a theater marquee effect in various colors...
            theaterChase(strip.Color(127, 127, 127), 50); // White, half brightness
            theaterChase(strip.Color(127,   0,   0), 50); // Red, half brightness
            theaterChase(strip.Color(  0,   0, 127), 50); // Blue, half brightness
          } else if(last_led_status == 3) {
             rainbow(10);  
          }else if(last_led_status == 4) {
             theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant
          }else {
             clearLED();
          }
        }          

        //处理wifi
        // check_wifi();
        //处理MQTT连接状态
        if(mqtt_status_bar_stepper % 4 == 0) {
          mqtt_status_bar_stepper = 0;
          is_transfer_mqtt = -1;
        }
        mqtt_status_bar_stepper ++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

//任务监控
static void task_monitor_process(void *arg)
{
    static const char *TASK4_TAG = "TASK_MONITOR";
    while (1) {
        // ESP_LOGI(TASK3_TAG, "task3_flag = %d, arg3 = %s", task3_flag, (char *)arg);
        updateTemp();
        //开启温控
        int tControlStatus = ui.param[AUTO_TEMP_CONTROL];
        int triggerTemp = ui.param[TRIGGER_TEMPERATURE];
        int fRPM = ui.param[TRIGGER_FAN_RPM];

        if( tControlStatus == 1){
          if(cTemp > triggerTemp){
            analogWrite(FAN1_SPEED_SET_PIN, fRPM*0.01*255);
            //设定灯光效果
            theaterChaseRainbow(50);
          }else {
            //拿出原来设定温度
            int beforeTemp = EEPROM.read(16);
            //analogWrite(FAN1_SPEED_SET_PIN, beforeTemp*0.01*255);
            clearLED();
          }
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}


void init_task_mgr()
{
    xTaskCreate(&task1_process, "task1", 1024*2, (void *)"1", configMAX_PRIORITIES-1, &task1); // configMAX_PRIORITIES = 25
    xTaskCreate(&task2_process, "task2", 1024*2, (void *)"2", configMAX_PRIORITIES-2, &task2);
    xTaskCreate(&task3_process, "task3", 1024*2, (void *)"3", configMAX_PRIORITIES-3, &task3);
    xTaskCreate(&task_monitor_process, "task4", 1024*2, (void *)"3", configMAX_PRIORITIES, &task_monitor);

    // xTaskCreatePinnedToCore(&task3_process, "task3", 1024*2, (void *)"3", configMAX_PRIORITIES-2, &task3, 0);
}

/********************************** FREERTOS函数 END **********************************/

/********************************** 分页面处理函数 **********************************/

//睡眠页面处理函数
void sleep_proc()
{
  // while (ui.sleep)
  // {
    //睡眠时循环执行的函数

    //睡眠时需要扫描旋钮才能退出睡眠
    btn_scan();

    //当旋钮有动作时
    if (btn.pressed) { 
      Serial.print("save all data.");      
      eeprom_write_all_data();
      for (uint8_t i = 0; i < UI_PARAM; ++i) {        
        Serial.print(i);    
        Serial.print("=");
        Serial.println(EEPROM.read(i));
      }

      btn.pressed = false; 
      switch (btn.id) {    

        //睡眠时顺时针旋转执行的函数
        case BTN_ID_CW:
          switch (knob.param[KNOB_ROT])
          {
            // case KNOB_ROT_VOL: Consumer.press(HIDConsumer::VOLUME_UP);       Consumer.release(); break;
            // case KNOB_ROT_BRI: Consumer.press(HIDConsumer::BRIGHTNESS_UP);   Consumer.release(); break;
          }
          break;

        //睡眠时逆时针旋转执行的函数
        case BTN_ID_CC:
          switch (knob.param[KNOB_ROT])
          {
            // case KNOB_ROT_VOL: Consumer.press(HIDConsumer::VOLUME_DOWN);     Consumer.release(); break;
            // case KNOB_ROT_BRI: Consumer.press(HIDConsumer::BRIGHTNESS_DOWN); Consumer.release(); break;
          }
          break;

        //睡眠时短按执行的函数
        // case BTN_ID_SP: 
        //   // Keyboard.press(knob.param[KNOB_COD]);  Keyboard.release(knob.param[KNOB_COD]); 
        // break;   
        
        //睡眠时长按执行的函数
        case BTN_ID_SP: ui.index = M_MAIN;  ui.state = S_LAYER_IN; u8g2.setPowerSave(0); ui.sleep = false; break;
      }
    }
  // }
}

//主菜单处理函数，磁贴类模板
void main_proc()
{
  tile_show(main_menu, main_icon_pic);
  if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: tile_rotate_switch(); break; case BTN_ID_SP: switch (ui.select[ui.layer]) {

        case 0: ui.index = M_MONITOR;   ui.state = S_LAYER_IN; break;
        case 1: ui.index = M_FAN_SETTING;  ui.state = S_LAYER_IN;  break;
        case 2: ui.index = M_SYS_SETTING;    ui.state = S_LAYER_IN;  break;
        case 3: ui.index = M_SLEEP; ui.state = S_LAYER_IN;  break;
      }
    }
    if (!tile.select_flag && ui.init) { tile.indi_x = 0; tile.title_y = tile.title_y_calc; }
  }
}

//编辑器菜单处理函数
void editor_proc()
{
  list_show(editor_menu, M_EDITOR); 
  if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break; case BTN_ID_LP: ui.select[ui.layer] = 0; case BTN_ID_SP: switch (ui.select[ui.layer]) {
        
        case 0:   ui.index = M_MAIN;  ui.state = S_LAYER_OUT; break;
        case 11:  ui.index = M_KNOB;  ui.state = S_LAYER_IN;  break;
      }
    }
  }
}

//简化版风速设计
void fan_setting_simple_proc()
{
  list_show(fan_setting_menu, M_FAN_SETTING); 
  if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break; case BTN_ID_LP: ui.select[ui.layer] = 0; case BTN_ID_SP: switch (ui.select[ui.layer]) {
        
        case 0:   ui.index = M_MAIN;  ui.state = S_LAYER_OUT; break;
        case 1:   window_value_init("风扇1", FAN1_SPEED, &ui.param[FAN1_SPEED],  100,  0,  1, fan_setting_menu, M_FAN_SETTING);  break;
        case 2:   window_value_init("风扇2", FAN2_SPEED, &ui.param[FAN2_SPEED],  100,  0,  1, fan_setting_menu, M_FAN_SETTING);  break;
        case 3:   window_value_init("风扇3", FAN3_SPEED, &ui.param[FAN3_SPEED],  100,  0,  1, fan_setting_menu, M_FAN_SETTING);  break;
        case 4:   window_value_init("风扇4", FAN4_SPEED, &ui.param[FAN4_SPEED],  100,  0,  1, fan_setting_menu, M_FAN_SETTING);  break;
        case 11:  ui.index = M_KNOB;  ui.state = S_LAYER_IN;  break;
      }
    }
  }
}

//旋钮编辑菜单处理函数
// void knob_proc()
// {
//   list_show(knob_menu, M_KNOB);
//   if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break; case BTN_ID_LP: ui.select[ui.layer] = 0; case BTN_ID_SP: switch (ui.select[ui.layer]) {
        
//         case 0: ui.index = M_EDITOR;  ui.state = S_LAYER_OUT; break;
//         case 1: ui.index = M_KRF;     ui.state = S_LAYER_IN;  check_box_s_init(&knob.param[KNOB_ROT], &knob.param[KNOB_ROT_P]); break;
//         case 2: ui.index = M_KPF;     ui.state = S_LAYER_IN;  check_box_s_init(&knob.param[KNOB_COD], &knob.param[KNOB_COD_P]); break;
//       }
//     }
//   }
// }

//旋钮旋转功能菜单处理函数
// void krf_proc()
// {
//   list_show(krf_menu, M_KRF);
//   if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break; case BTN_ID_LP: ui.select[ui.layer] = 0; case BTN_ID_SP: switch (ui.select[ui.layer]) {
        
//         case 0: ui.index = M_KNOB;  ui.state = S_LAYER_OUT; break;
//         case 1: break;
//         case 2: check_box_s_select(KNOB_DISABLE, ui.select[ui.layer]); break;
//         case 3: break;
//         case 4: check_box_s_select(KNOB_ROT_VOL, ui.select[ui.layer]); break;
//         case 5: check_box_s_select(KNOB_ROT_BRI, ui.select[ui.layer]); break;
//         case 6: break;
//       }
//     }
//   }
// }

//旋钮点按功能菜单处理函数
// void kpf_proc()
// {
//   list_show(kpf_menu, M_KPF);
//   if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break;  case BTN_ID_LP: ui.select[ui.layer] = 0; case BTN_ID_SP: switch (ui.select[ui.layer]) {
    
//         case 0:   ui.index = M_KNOB;  ui.state = S_LAYER_OUT; break;
//         case 1:   break;
//         case 2:   check_box_s_select(KNOB_DISABLE, ui.select[ui.layer]); break;
//         case 3:   break;
//         case 4:   check_box_s_select('A', ui.select[ui.layer]); break;
//         case 5:   check_box_s_select('B', ui.select[ui.layer]); break;
//         case 6:   check_box_s_select('C', ui.select[ui.layer]); break;
//         case 7:   check_box_s_select('D', ui.select[ui.layer]); break;
//         case 8:   check_box_s_select('E', ui.select[ui.layer]); break;
//         case 9:   check_box_s_select('F', ui.select[ui.layer]); break;
//         case 10:  check_box_s_select('G', ui.select[ui.layer]); break;
//         case 11:  check_box_s_select('H', ui.select[ui.layer]); break;
//         case 12:  check_box_s_select('I', ui.select[ui.layer]); break;
//         case 13:  check_box_s_select('J', ui.select[ui.layer]); break;
//         case 14:  check_box_s_select('K', ui.select[ui.layer]); break;
//         case 15:  check_box_s_select('L', ui.select[ui.layer]); break;
//         case 16:  check_box_s_select('M', ui.select[ui.layer]); break;
//         case 17:  check_box_s_select('N', ui.select[ui.layer]); break;
//         case 18:  check_box_s_select('O', ui.select[ui.layer]); break;
//         case 19:  check_box_s_select('P', ui.select[ui.layer]); break;
//         case 20:  check_box_s_select('Q', ui.select[ui.layer]); break;
//         case 21:  check_box_s_select('R', ui.select[ui.layer]); break;
//         case 22:  check_box_s_select('S', ui.select[ui.layer]); break;
//         case 23:  check_box_s_select('T', ui.select[ui.layer]); break;
//         case 24:  check_box_s_select('U', ui.select[ui.layer]); break;
//         case 25:  check_box_s_select('V', ui.select[ui.layer]); break;
//         case 26:  check_box_s_select('W', ui.select[ui.layer]); break;
//         case 27:  check_box_s_select('X', ui.select[ui.layer]); break;
//         case 28:  check_box_s_select('Y', ui.select[ui.layer]); break;
//         case 29:  check_box_s_select('Z', ui.select[ui.layer]); break;
//         case 30:  break;
//         case 31:  check_box_s_select('0', ui.select[ui.layer]); break;
//         case 32:  check_box_s_select('1', ui.select[ui.layer]); break;
//         case 33:  check_box_s_select('2', ui.select[ui.layer]); break;
//         case 34:  check_box_s_select('3', ui.select[ui.layer]); break;
//         case 35:  check_box_s_select('4', ui.select[ui.layer]); break;
//         case 36:  check_box_s_select('5', ui.select[ui.layer]); break;
//         case 37:  check_box_s_select('6', ui.select[ui.layer]); break;
//         case 38:  check_box_s_select('7', ui.select[ui.layer]); break;
//         case 39:  check_box_s_select('8', ui.select[ui.layer]); break;
//         case 40:  check_box_s_select('9', ui.select[ui.layer]); break;
//         case 41:  break;
//       }
//     }
//   }
// }

//电压测量页处理函数
// void volt_proc()
// {
//   volt_show();
//   if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break;

//       case BTN_ID_SP: case BTN_ID_LP: ui.index = M_MAIN;  ui.state = S_LAYER_OUT; break;
//     }
//   }
// }

//设置菜单处理函数，多选框列表类模板，弹窗模板
// void setting_proc()
// {
//   list_show(setting_menu, M_SETTING);
//   if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break; case BTN_ID_LP: ui.select[ui.layer] = 0; case BTN_ID_SP: switch (ui.select[ui.layer]) {
        
//         //返回更浅层级，长按被当作选择这一项，也是执行这一行
//         case 0:   ui.index = M_MAIN;  ui.state = S_LAYER_OUT; break;
        
//         //弹出窗口，参数初始化：标题，参数名，参数值，最大值，最小值，步长，背景列表名，背景列表标签
//         case 1:   window_value_init("Disp Bri", DISP_BRI, &ui.param[DISP_BRI],  255,  0,  5, setting_menu, M_SETTING);  break;
//         case 2:   window_value_init("Tile Ani", TILE_ANI, &ui.param[TILE_ANI],  100, 10,  1, setting_menu, M_SETTING);  break;
//         case 3:   window_value_init("List Ani", LIST_ANI, &ui.param[LIST_ANI],  100, 10,  1, setting_menu, M_SETTING);  break;
//         case 4:   window_value_init("Win Ani",  WIN_ANI,  &ui.param[WIN_ANI],   100, 10,  1, setting_menu, M_SETTING);  break;
//         case 5:   window_value_init("Spot Ani", SPOT_ANI, &ui.param[SPOT_ANI],  100, 10,  1, setting_menu, M_SETTING);  break;
//         case 6:   window_value_init("Tag Ani",  TAG_ANI,  &ui.param[TAG_ANI],   100, 10,  1, setting_menu, M_SETTING);  break;
//         case 7:   window_value_init("Fade Ani", FADE_ANI, &ui.param[FADE_ANI],  255,  0,  1, setting_menu, M_SETTING);  break;
//         case 8:   window_value_init("Btn SPT",  BTN_SPT,  &ui.param[BTN_SPT],   255,  0,  1, setting_menu, M_SETTING);  break;
//         case 9:   window_value_init("Btn LPT",  BTN_LPT,  &ui.param[BTN_LPT],   255,  0,  1, setting_menu, M_SETTING);  break;

//         //多选框
//         case 10:  check_box_m_select( TILE_UFD  );  break;
//         case 11:  check_box_m_select( LIST_UFD  );  break;
//         case 12:  check_box_m_select( TILE_LOOP );  break;
//         case 13:  check_box_m_select( LIST_LOOP );  break;
//         case 14:  check_box_m_select( WIN_BOK   );  break;
//         case 15:  check_box_m_select( KNOB_DIR  );  break;
//         case 16:  check_box_m_select( DARK_MODE );  break;

//         //关于本机
//         case 17:  ui.index = M_ABOUT; ui.state = S_LAYER_IN; break;
//       }
//     }
//   }
// }

//关于本机页
void about_proc()
{
  list_show(about_menu, M_ABOUT);
  if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break; case BTN_ID_LP: ui.select[ui.layer] = 0; case BTN_ID_SP: switch (ui.select[ui.layer]) {

        case 0:   ui.index = M_SYS_SETTING;  ui.state = S_LAYER_OUT; break;
      }
    }
  }
}

//系统设置
void sys_setting_proc()
{
  list_show(sys_setting_menu, M_SYS_SETTING);

  if (btn.pressed) { btn.pressed = false; switch (btn.id) { case BTN_ID_CW: case BTN_ID_CC: list_rotate_switch(); break; case BTN_ID_LP: ui.select[ui.layer] = 0; case BTN_ID_SP: switch (ui.select[ui.layer]) {
        
        //返回更浅层级，长按被当作选择这一项，也是执行这一行
        case 0:   ui.index = M_MAIN;  ui.state = S_LAYER_OUT; break;
        case 1:  check_box_m_select( IS_UPDATE  );  break;
        case 2:  check_box_m_select( IS_REMOTE_CONTROL  );  break;
        case 3:   window_value_init("灯光管理", LED_TYPE, &ui.param[LED_TYPE],  5,  0,  1, sys_setting_menu, M_SYS_SETTING);  break;
        case 4:   window_value_init("休眠时间", SLEEP_TIME, &ui.param[SLEEP_TIME],  250, 30,  5, sys_setting_menu, M_SYS_SETTING);  break;
        case 5:   check_box_m_select( AUTO_TEMP_CONTROL  );  break;
        case 6:   window_value_init("触发温度℃", TRIGGER_TEMPERATURE, &ui.param[TRIGGER_TEMPERATURE],  45, 10,  1, sys_setting_menu, M_SYS_SETTING);  break;
        case 7:   window_value_init("设定风扇转速", TRIGGER_FAN_RPM, &ui.param[TRIGGER_FAN_RPM],  100, 0,  1, sys_setting_menu, M_SYS_SETTING);  break;
        //关于本机
        case 8:  ui.index = M_ABOUT; ui.state = S_LAYER_IN; break;
      }
    }
  }
}



//监控风扇面板
void monitor_proc()
{
  last_sleep_status = -1;

  int y=1;//调节位移速度，可用于加快字符位移速度
  // for(int x=0;x<256;x+=y){//x+=y ----->等价于x=x+y    
    u8g2.clearBuffer();
    
    //绘制状态栏
    u8g2.setFont(u8g2_font_siji_t_6x10);

    //判断是否已经连上wifi
    if(is_connect_wifi == -1) {
      //wifi未连上
      u8g2.drawGlyph(0, 9, 0xe217);	
    } else {
      u8g2.drawGlyph(0, 9, 0xe21a);
    }
    //如果开始MQTT通信，则显示传输信号图标
    if(is_transfer_mqtt == -1){
        u8g2.drawStr(12, 9, "");
     } else {
        u8g2.drawGlyph(12, 9, 0xe10f);        
      // delay(500);
      // u8g2.drawGlyph(10, 9, 0x0020);
      // delay(500);
      // u8g2.drawGlyph(10, 9, 0xe12f);
    }
    
    //湿度图标
    u8g2.drawGlyph(80, 19, 0xe01b);

    u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    u8g2.setCursor(95, 20);
    // u8g2.print("H:");
    u8g2.print(humidity);

    u8g2.setFont(u8g2_font_logisoso26_tn);
    u8g2.setCursor(0, 42);
    u8g2.print(cTemp);

    u8g2.setFont(u8g2_font_wqy16_t_gb2312);
    u8g2.setCursor(85, 41);  
    u8g2.print("℃");

    u8g2.setFont(u8g2_font_wqy12_t_gb2312);    
    // u8g2.setCursor(-x, 60);  
    if (monitor_fan_x >= 256) {
      monitor_fan_x = 0;
    }
    u8g2.setCursor(-monitor_fan_x, 60);  
    int f1 = ui.param[FAN1_SPEED];
    int f2 = ui.param[FAN2_SPEED];
    int f3 = ui.param[FAN3_SPEED];
    int f4 = ui.param[FAN4_SPEED];
    u8g2.printf("1号风扇:%d%% 2号风扇:%d%% 3号风扇:%d%% 4号风扇:%d%%", f1, f2, f3, f4);
    delay(20);//延时程序，可以降低位移速度
    u8g2.sendBuffer();
  // }
    
    //blinkGreenLED();

   if (btn.pressed) { 
      btn.pressed = false; 
      switch (btn.id) {    

        //睡眠时顺时针旋转执行的函数
        case BTN_ID_CW:
          switch (knob.param[KNOB_ROT])
          {
            // case KNOB_ROT_VOL: Consumer.press(HIDConsumer::VOLUME_UP);       Consumer.release(); break;
            // case KNOB_ROT_BRI: Consumer.press(HIDConsumer::BRIGHTNESS_UP);   Consumer.release(); break;
          }
          break;

        //睡眠时逆时针旋转执行的函数
        case BTN_ID_CC:
          switch (knob.param[KNOB_ROT])
          {
            // case KNOB_ROT_VOL: Consumer.press(HIDConsumer::VOLUME_DOWN);     Consumer.release(); break;
            // case KNOB_ROT_BRI: Consumer.press(HIDConsumer::BRIGHTNESS_DOWN); Consumer.release(); break;
          }
          break;

        //睡眠时短按执行的函数
        // case BTN_ID_SP: 
        //   // Keyboard.press(knob.param[KNOB_COD]);  Keyboard.release(knob.param[KNOB_COD]); 
        // break;   
        
        //睡眠时长按执行的函数
        case BTN_ID_SP: ui.index = M_MAIN;  ui.state = S_LAYER_IN; u8g2.setPowerSave(0); ui.sleep = false; break;
      }
    }
  // }
  
}

//设置风扇速度
void fan_speed_proc()
{
  int FAN_SPEED_RATE_Y = 38;
  int FAN_SPEED_RATE_X = 40;
  int ii = 56;
  //for( int ii = 0 ; ii <= 100; ii ++) {
      u8g2.clearBuffer();
      //绘制返回图标
      u8g2.setFont(u8g2_font_siji_t_6x10);
      u8g2.drawGlyph(0, 9, 0xe07f);	

      //设置中文字体
      u8g2.setFont(u8g2_font_wqy16_t_gb2312);
      u8g2.setCursor(0, 37);  
      // u8g2.print("设置:");

      //数字时钟字体
      u8g2.setFont(u8g2_font_logisoso26_tn);
      u8g2.setCursor(FAN_SPEED_RATE_X, FAN_SPEED_RATE_Y);
      u8g2.print(ii);

      u8g2.setFont(u8g2_font_logisoso24_tr);
      if(ii < 10) {
         u8g2.setCursor(FAN_SPEED_RATE_X + 22, FAN_SPEED_RATE_Y); 
      } else if(ii >= 10 && ii <100)
      {
        u8g2.setCursor(FAN_SPEED_RATE_X + 35, FAN_SPEED_RATE_Y); 
      }else{
        //如果是100%
        u8g2.setCursor(FAN_SPEED_RATE_X + 50, FAN_SPEED_RATE_Y);   
      }
      u8g2.print("%");

      //底部文字
      u8g2.setFont(u8g2_font_wqy12_t_gb2312);
      u8g2.setCursor(20, 62);  
      u8g2.print("风扇1 转速:2034");

      u8g2.sendBuffer();
      //delay(500);
  //}  
  if (btn.pressed) { btn.pressed = false; 
    switch (btn.id) { 
      case BTN_ID_CW: 
        case BTN_ID_CC: 
        {
            list_rotate_switch(); 
        }  
        break; 
      case BTN_ID_LP: 
      {
        ui.select[ui.layer] = 0; 
      }
      case BTN_ID_SP: 
      {
          switch (ui.select[ui.layer]) {
              
              //返回更浅层级，长按被当作选择这一项，也是执行这一行
              case 0:   ui.index = M_MAIN;  ui.state = S_LAYER_OUT; break;
              case 1:  check_box_m_select( IS_UPDATE  );  break;
              case 2:  check_box_m_select( IS_REMOTE_CONTROL  );  break;
              case 3:   window_value_init("灯光管理", LED_TYPE, &ui.param[LED_TYPE],  5,  0,  1, sys_setting_menu, M_SYS_SETTING);  break;
              case 4:   window_value_init("休眠时间", SLEEP_TIME, &ui.param[SLEEP_TIME],  30, 0,  1, sys_setting_menu, M_SYS_SETTING);  break;
              case 5:   window_value_init("触发温度℃", TRIGGER_TEMPERATURE, &ui.param[TRIGGER_TEMPERATURE],  50, 10,  1, sys_setting_menu, M_SYS_SETTING);  break;
              //关于本机
              case 6:  ui.index = M_ABOUT; ui.state = S_LAYER_IN; break;
          }
      }
    }
  }
}


//总的UI进程
void ui_proc()
{
  u8g2.sendBuffer();
  switch (ui.state)
  {
    case S_FADE:          fade();                   break;  //转场动画
    case S_WINDOW:        window_param_init();      break;  //弹窗初始化
    case S_LAYER_IN:      layer_init_in();          break;  //层级初始化
    case S_LAYER_OUT:     layer_init_out();         break;  //层级初始化
  
    case S_NONE: u8g2.clearBuffer(); switch (ui.index)      //直接选择页面
    {
      case M_WINDOW:      window_proc();            break;
      case M_SLEEP:       sleep_proc();             break;
      case M_MAIN:        main_proc();              break;
      case M_EDITOR:      editor_proc();            break;
      // case M_KNOB:        knob_proc();              break;
      // case M_KRF:         krf_proc();               break;
      // case M_KPF:         kpf_proc();               break;
      // case M_VOLT:        volt_proc();              break;
      // case M_SETTING:     setting_proc();           break;
      case M_ABOUT:       about_proc();             break;
      case M_FAN_SETTING:  fan_setting_simple_proc();        break;
      case M_MONITOR:      monitor_proc();          break;
      case M_SYS_SETTING:  sys_setting_proc();      break;      
    }
  }
}

int current_disp = -1;
void handle_event()
{
    if( current_disp != ui.param[DISP_BRI]){
      current_disp = ui.param[DISP_BRI];
    }        
}

void init_operate()
{
  ui.index = M_MAIN;  
  ui.state = S_LAYER_IN; 
  u8g2.setPowerSave(0); 
  ui.sleep = true;
}



//OLED初始化函数
void oled_init()
{
  
  u8g2.setBusClock(800000);  //硬件IIC接口使用
  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setContrast(ui.param[DISP_BRI]);
  buf_ptr = u8g2.getBufferPtr();
  buf_len = 8 * u8g2.getBufferTileHeight() * u8g2.getBufferTileWidth();

  init_logo();
  delay(1000);
  init_operate();
}

void init_logo()
{
  u8g2.drawXBMP(0,0, logo_width, logo_height, logo_bits);
  u8g2.sendBuffer();
  u8g2.clearBuffer();
}

//更新 SHT30
void updateTemp()
{
   unsigned int data[6]; //存储获取到的六个数据
  //开始IIC
  //写地址
  Wire.beginTransmission(Addr_SHT30);
  //发送测量命令 0x2C06,但是因为IIC一次只能发一个8位数据，所以得发两次
  Wire.write(0x2C);
  Wire.write(0x06);
  //停止IIC
  Wire.endTransmission();
  //等待500ms是等待SHT30器件测量数据，实际上这个时间可以很短
  delay(500);

  //请求获取6字节的数据，然后会存到8266的内存里
  Wire.requestFrom(Addr_SHT30, 6);

  //读取6字节的数据
  //这六个字节分别为：温度8位高数据，温度8位低数据，温度8位CRC校验数据
  //               湿度8位高数据，湿度8位低数据，湿度8位CRC校验数据
  if (Wire.available() == 6)
  {
    data[0] = Wire.read();
    data[1] = Wire.read();
    data[2] = Wire.read();
    data[3] = Wire.read();
    data[4] = Wire.read();
    data[5] = Wire.read();
  }

  //然后计算得到的数据，要转化为摄氏度、华氏度、相对湿度
  cTemp = ((((data[0] * 256.0) + data[1]) * 175) / 65535.0) - 45;
  fTemp = (cTemp * 1.8) + 32;
  humidity = ((((data[3] * 256.0) + data[4]) * 100) / 65535.0);
}

void init_sht30()
{
   Wire.begin(SDA, SCL);
}

//初始化LED
void initLED()
{
  #if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
  #endif
  // END of Trinket-specific code.
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(15); // Set BRIGHTNESS to about 1/5 (max = 255)
  Serial.println("初始化灯带....");
}

void setup() 
{
  init_basic_wifi();
  eeprom_init();
  ui_init();
  oled_init();
  btn_init();
  init_sht30();
  initLED();
  init_task_mgr();  
  init_fans();
  //hid_init();
  
  Serial.begin(9600);
}

void loop() 
{
  //updateTemp();  
  btn_scan();
  ui_proc();
  handle_event();  
}